<commit_before>/**
 * @file transferslot.cpp
 * @brief Class for active transfer
 *
 * (c) 2013-2014 by Mega Limited, Auckland, New Zealand
 *
 * This file is part of the MEGA SDK - Client Access Engine.
 *
 * Applications using the MEGA API must present a valid application key
 * and comply with the the rules set forth in the Terms of Service.
 *
 * The MEGA SDK 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.
 *
 * @copyright Simplified (2-clause) BSD License.
 *
 * You should have received a copy of the license along with this
 * program.
 */

#include "mega/transferslot.h"
#include "mega/node.h"
#include "mega/transfer.h"
#include "mega/megaclient.h"
#include "mega/command.h"
#include "mega/base64.h"
#include "mega/megaapp.h"
#include "mega/utils.h"
#include "mega/logging.h"

namespace mega {
TransferSlot::TransferSlot(Transfer* ctransfer)
{
    starttime = 0;
    progressreported = 0;
    lastdata = Waiter::ds;
    errorcount = 0;

    failure = false;
    retrying = false;

    fileattrsmutable = 0;

    reqs = NULL;
    pendingcmd = NULL;

    transfer = ctransfer;
    transfer->slot = this;

    connections = transfer->size > 131072 ? transfer->client->connections[transfer->type] : 1;

    reqs = new HttpReqXfer*[connections]();

    fa = transfer->client->fsaccess->newfileaccess();

    slots_it = transfer->client->tslots.end();
}

// delete slot and associated resources, but keep transfer intact (can be
// reused on a new slot)
TransferSlot::~TransferSlot()
{
    if (transfer->client->tctable && transfer->type == GET && !transfer->finished)
    {
        m_off_t p = 0;
        for (int i = 0; i < connections; i++)
        {
            if (fa && reqs[i] && reqs[i]->status == REQ_INFLIGHT
                    && reqs[i]->contentlength == reqs[i]->size
                    && reqs[i]->bufpos >= SymmCipher::BLOCKSIZE)
            {
                m_off_t bufpos = reqs[i]->bufpos & -SymmCipher::BLOCKSIZE;
                m_off_t dlpos = ((HttpReqDL *)reqs[i])->dlpos;
                ChunkMAC &chunk = transfer->chunkmacs[reqs[i]->pos];

                p += bufpos;
                transfer->key.ctr_crypt(reqs[i]->buf, bufpos, dlpos, transfer->ctriv,
                            chunk.mac, false, !chunk.finished && !chunk.offset);

                fa->fwrite((const byte*)reqs[i]->buf, bufpos, dlpos);
                chunk.offset += bufpos;
            }
        }

        if (p)
        {
            transfer->client->transfercacheadd(transfer);
            LOG_debug << "Completed: " << (transfer->progresscompleted + p)
                      << " Partial: " << p;
        }
    }

    transfer->slot = NULL;

    if (slots_it != transfer->client->tslots.end())
    {
        // advance main loop iterator if deleting next in line
        if (transfer->client->slotit != transfer->client->tslots.end() && *transfer->client->slotit == this)
        {
            transfer->client->slotit++;
        }

        transfer->client->tslots.erase(slots_it);
    }

    if (pendingcmd)
    {
        pendingcmd->cancel();
    }

    if (fa)
    {
        delete fa;
    }

    while (connections--)
    {
        delete reqs[connections];
    }

    delete[] reqs;
}

void TransferSlot::toggleport(HttpReqXfer *req)
{
    if (!memcmp(req->posturl.c_str(), "http:", 5))
    {
       size_t portendindex = req->posturl.find("/", 8);
       size_t portstartindex = req->posturl.find(":", 8);

       if (portendindex != string::npos)
       {
           if (portstartindex == string::npos)
           {
               LOG_debug << "Enabling alternative port for chunk";
               req->posturl.insert(portendindex, ":8080");
           }
           else
           {
               LOG_debug << "Disabling alternative port for chunk";
               req->posturl.erase(portstartindex, portendindex - portstartindex);
           }
       }
    }
}

// abort all HTTP connections
void TransferSlot::disconnect()
{
    for (int i = connections; i--;)
    {
        if (reqs[i])
        {
            reqs[i]->disconnect();
        }
    }
}

// coalesce block macs into file mac
int64_t TransferSlot::macsmac(chunkmac_map* macs)
{
    byte mac[SymmCipher::BLOCKSIZE] = { 0 };

    for (chunkmac_map::iterator it = macs->begin(); it != macs->end(); it++)
    {
        SymmCipher::xorblock(it->second.mac, mac);
        transfer->key.ecb_encrypt(mac);
    }

    uint32_t* m = (uint32_t*)mac;

    m[0] ^= m[1];
    m[1] = m[2] ^ m[3];

    return MemAccess::get<int64_t>((const char*)mac);
}

// file transfer state machine
void TransferSlot::doio(MegaClient* client)
{
    if (!fa || (transfer->size && transfer->progresscompleted == transfer->size))
    {
        // this is a pending completion, retry every 200 ms by default
        retrybt.backoff(2);
        retrying = true;

        return transfer->complete();
    }

    retrying = false;

    if (!tempurl.size())
    {
        return;
    }

    dstime backoff = 0;
    m_off_t p = 0;

    if (errorcount > 4)
    {
        return transfer->failed(API_EFAILED);
    }

    for (int i = connections; i--; )
    {
        if (reqs[i])
        {
            switch (reqs[i]->status)
            {
                case REQ_INFLIGHT:
                    p += reqs[i]->transferred(client);
                    break;

                case REQ_SUCCESS:
                    lastdata = Waiter::ds;
                    transfer->lastaccesstime = time(NULL);
                    transfer->progresscompleted += reqs[i]->size;

                    LOG_debug << "Chunk finished OK (" << transfer->type << ") Pos: " << transfer->pos
                              << " Completed: " << transfer->progresscompleted << " of " << transfer->size;

                    if (transfer->type == PUT)
                    {
                        errorcount = 0;
                        transfer->failcount = 0;
                        transfer->chunkmacs[reqs[i]->pos].finished = true;

                        // completed put transfers are signalled through the
                        // return of the upload token
                        if (reqs[i]->in.size())
                        {
                            if (reqs[i]->in.size() == NewNode::UPLOADTOKENLEN * 4 / 3)
                            {
                                LOG_debug << "Upload token received";
                                if (Base64::atob(reqs[i]->in.data(), transfer->ultoken, NewNode::UPLOADTOKENLEN + 1)
                                    == NewNode::UPLOADTOKENLEN)
                                {
                                    memcpy(transfer->filekey, transfer->key.key, sizeof transfer->key.key);
                                    ((int64_t*)transfer->filekey)[2] = transfer->ctriv;
                                    ((int64_t*)transfer->filekey)[3] = macsmac(&transfer->chunkmacs);
                                    SymmCipher::xorblock(transfer->filekey + SymmCipher::KEYLENGTH, transfer->filekey);

                                    client->transfercacheadd(transfer);
                                    return transfer->complete();
                                }
                            }

                            LOG_debug << "Invalid upload token: " << reqs[i]->in;
                            transfer->progresscompleted -= reqs[i]->size;
                            transfer->chunkmacs[reqs[i]->pos].finished = false;

                            // fail with returned error
                            return transfer->failed((error)atoi(reqs[i]->in.c_str()));
                        }
                    }
                    else
                    {
                        if (reqs[i]->size == reqs[i]->bufpos)
                        {
                            errorcount = 0;
                            transfer->failcount = 0;

                            reqs[i]->finalize(fa, &transfer->key, &transfer->chunkmacs, transfer->ctriv, 0, -1);

                            if (transfer->progresscompleted == transfer->size)
                            {
                                // verify meta MAC
                                if (!transfer->progresscompleted || (macsmac(&transfer->chunkmacs) == transfer->metamac))
                                {
                                    client->transfercacheadd(transfer);
                                    return transfer->complete();
                                }
                                else
                                {
                                    LOG_warn << "MAC verification failed";
                                    transfer->chunkmacs.clear();
                                    transfer->progresscompleted -= reqs[i]->size;
                                    return transfer->failed(API_EKEY);
                                }
                            }
                        }
                        else
                        {
                            LOG_warn << "Invalid chunk size: " << reqs[i]->size << " - " << reqs[i]->bufpos;
                            transfer->progresscompleted -= reqs[i]->size;
                            errorcount++;
                            reqs[i]->status = REQ_PREPARED;
                            break;
                        }
                    }

                    client->transfercacheadd(transfer);
                    reqs[i]->status = REQ_READY;
                    break;

                case REQ_FAILURE:
                    LOG_warn << "Failed chunk. HTTP status: " << reqs[i]->httpstatus;
                    if (reqs[i]->httpstatus == 509)
                    {
                        if (reqs[i]->timeleft < 0)
                        {
                            int creqtag = client->reqtag;
                            client->reqtag = 0;
                            client->sendevent(99408, "Overquota without timeleft");
                            client->reqtag = creqtag;
                        }

                        LOG_warn << "Bandwidth overquota from storage server";
                        if (reqs[i]->timeleft > 0)
                        {
                            backoff = reqs[i]->timeleft * 10;
                        }
                        else
                        {
                            // default retry intervals
                            backoff = MegaClient::DEFAULT_BW_OVERQUOTA_BACKOFF_SECS * 10;
                        }

                        return transfer->failed(API_EOVERQUOTA, backoff);
                    }
                    else if (reqs[i]->httpstatus == 403 || reqs[i]->httpstatus == 404)
                    {
                        return transfer->failed(API_EAGAIN);
                    }
                    else
                    {
                        if (!failure)
                        {
                            failure = true;
                            bool changeport = false;

                            if (transfer->type == GET && client->autodownport && !memcmp(tempurl.c_str(), "http:", 5))
                            {
                                LOG_debug << "Automatically changing download port";
                                client->usealtdownport = !client->usealtdownport;
                                changeport = true;
                            }
                            else if (transfer->type == PUT && client->autoupport && !memcmp(tempurl.c_str(), "http:", 5))
                            {
                                LOG_debug << "Automatically changing upload port";
                                client->usealtupport = !client->usealtupport;
                                changeport = true;
                            }

                            client->app->transfer_failed(transfer, API_EFAILED);
                            client->setchunkfailed(&reqs[i]->posturl);

                            if (changeport)
                            {
                                toggleport(reqs[i]);
                            }
                        }
                    }
                    reqs[i]->status = REQ_PREPARED;

                default:
                    ;
            }
        }

        if (!failure)
        {
            if (!reqs[i] || (reqs[i]->status == REQ_READY))
            {
                m_off_t npos = ChunkedHash::chunkceil(transfer->nextpos());

                if (npos > transfer->size)
                {
                    npos = transfer->size;
                }

                if (!transfer->size)
                {
                    transfer->pos = 0;
                }

                if ((npos > transfer->pos) || !transfer->size)
                {
                    if (!reqs[i])
                    {
                        reqs[i] = transfer->type == PUT ? (HttpReqXfer*)new HttpReqUL() : (HttpReqXfer*)new HttpReqDL();
                    }

                    string finaltempurl = tempurl;
                    if (transfer->type == GET && client->usealtdownport
                            && !memcmp(tempurl.c_str(), "http:", 5))
                    {
                        size_t index = tempurl.find("/", 8);
                        if(index != string::npos && tempurl.find(":", 8) == string::npos)
                        {
                            finaltempurl.insert(index, ":8080");
                        }
                    }

                    if (transfer->type == PUT && client->usealtupport
                            && !memcmp(tempurl.c_str(), "http:", 5))
                    {
                        size_t index = tempurl.find("/", 8);
                        if(index != string::npos && tempurl.find(":", 8) == string::npos)
                        {
                            finaltempurl.insert(index, ":8080");
                        }
                    }

                    if (reqs[i]->prepare(fa, finaltempurl.c_str(), &transfer->key,
                                         &transfer->chunkmacs, transfer->ctriv,
                                         transfer->pos, npos))
                    {
                        reqs[i]->pos = ChunkedHash::chunkfloor(transfer->pos);
                        reqs[i]->status = REQ_PREPARED;
                        transfer->pos = npos;
                    }
                    else
                    {
                        LOG_warn << "Error preparing transfer: " << fa->retry;
                        if (!fa->retry)
                        {
                            return transfer->failed(API_EREAD);
                        }

                        // retry the read shortly
                        backoff = 2;
                    }
                }
                else if (reqs[i])
                {
                    reqs[i]->status = REQ_DONE;
                }
            }

            if (reqs[i] && (reqs[i]->status == REQ_PREPARED))
            {
                reqs[i]->post(client);
            }
        }
    }

    p += transfer->progresscompleted;

    if (p != progressreported)
    {
        progressreported = p;
        lastdata = Waiter::ds;

        progress();
    }

    if (Waiter::ds - lastdata >= XFERTIMEOUT && !failure)
    {
        LOG_warn << "Failed chunk due to a timeout";
        failure = true;
        bool changeport = false;

        if (transfer->type == GET && client->autodownport && !memcmp(tempurl.c_str(), "http:", 5))
        {
            LOG_debug << "Automatically changing download port due to a timeout";
            client->usealtdownport = !client->usealtdownport;
            changeport = true;
        }
        else if (transfer->type == PUT && client->autoupport && !memcmp(tempurl.c_str(), "http:", 5))
        {
            LOG_debug << "Automatically changing upload port due to a timeout";
            client->usealtupport = !client->usealtupport;
            changeport = true;
        }

        bool chunkfailed = false;
        for (int i = connections; i--; )
        {
            if (reqs[i] && reqs[i]->status == REQ_INFLIGHT)
            {
                chunkfailed = true;
                client->setchunkfailed(&reqs[i]->posturl);
                reqs[i]->disconnect();

                if (changeport)
                {
                    toggleport(reqs[i]);
                }

                reqs[i]->status = REQ_PREPARED;
            }
        }

        if (!chunkfailed)
        {
            LOG_warn << "Transfer failed due to a timeout";
            transfer->failed(API_EAGAIN);
        }
        else
        {
            LOG_warn << "Chunk failed due to a timeout";
            client->app->transfer_failed(transfer, API_EFAILED);
        }
    }

    if (!failure)
    {
        if (!backoff && (Waiter::ds - lastdata) < XFERTIMEOUT)
        {
            // no other backoff: check again at XFERMAXFAIL
            backoff = XFERTIMEOUT - (Waiter::ds - lastdata);
        }

        retrybt.backoff(backoff);
    }
}

// transfer progress notification to app and related files
void TransferSlot::progress()
{
    transfer->client->app->transfer_update(transfer);

    for (file_list::iterator it = transfer->files.begin(); it != transfer->files.end(); it++)
    {
        (*it)->progress();
    }
}
} // namespace
<commit_msg>Save partial chunks even if persistent transfer resumption is not enabled Don't try to cache partial chunks if the transfer is already completed<commit_after>/**
 * @file transferslot.cpp
 * @brief Class for active transfer
 *
 * (c) 2013-2014 by Mega Limited, Auckland, New Zealand
 *
 * This file is part of the MEGA SDK - Client Access Engine.
 *
 * Applications using the MEGA API must present a valid application key
 * and comply with the the rules set forth in the Terms of Service.
 *
 * The MEGA SDK 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.
 *
 * @copyright Simplified (2-clause) BSD License.
 *
 * You should have received a copy of the license along with this
 * program.
 */

#include "mega/transferslot.h"
#include "mega/node.h"
#include "mega/transfer.h"
#include "mega/megaclient.h"
#include "mega/command.h"
#include "mega/base64.h"
#include "mega/megaapp.h"
#include "mega/utils.h"
#include "mega/logging.h"

namespace mega {
TransferSlot::TransferSlot(Transfer* ctransfer)
{
    starttime = 0;
    progressreported = 0;
    lastdata = Waiter::ds;
    errorcount = 0;

    failure = false;
    retrying = false;

    fileattrsmutable = 0;

    reqs = NULL;
    pendingcmd = NULL;

    transfer = ctransfer;
    transfer->slot = this;

    connections = transfer->size > 131072 ? transfer->client->connections[transfer->type] : 1;

    reqs = new HttpReqXfer*[connections]();

    fa = transfer->client->fsaccess->newfileaccess();

    slots_it = transfer->client->tslots.end();
}

// delete slot and associated resources, but keep transfer intact (can be
// reused on a new slot)
TransferSlot::~TransferSlot()
{
    if (transfer->type == GET && !transfer->finished
            && transfer->progresscompleted != transfer->size)
    {
        m_off_t p = 0;
        for (int i = 0; i < connections; i++)
        {
            if (fa && reqs[i] && reqs[i]->status == REQ_INFLIGHT
                    && reqs[i]->contentlength == reqs[i]->size
                    && reqs[i]->bufpos >= SymmCipher::BLOCKSIZE)
            {
                m_off_t bufpos = reqs[i]->bufpos & -SymmCipher::BLOCKSIZE;
                m_off_t dlpos = ((HttpReqDL *)reqs[i])->dlpos;
                ChunkMAC &chunk = transfer->chunkmacs[reqs[i]->pos];

                p += bufpos;
                transfer->key.ctr_crypt(reqs[i]->buf, bufpos, dlpos, transfer->ctriv,
                            chunk.mac, false, !chunk.finished && !chunk.offset);

                fa->fwrite((const byte*)reqs[i]->buf, bufpos, dlpos);
                chunk.offset += bufpos;
            }
        }

        if (p)
        {
            transfer->client->transfercacheadd(transfer);
            LOG_debug << "Completed: " << (transfer->progresscompleted + p)
                      << " Partial: " << p;
        }
    }

    transfer->slot = NULL;

    if (slots_it != transfer->client->tslots.end())
    {
        // advance main loop iterator if deleting next in line
        if (transfer->client->slotit != transfer->client->tslots.end() && *transfer->client->slotit == this)
        {
            transfer->client->slotit++;
        }

        transfer->client->tslots.erase(slots_it);
    }

    if (pendingcmd)
    {
        pendingcmd->cancel();
    }

    if (fa)
    {
        delete fa;
    }

    while (connections--)
    {
        delete reqs[connections];
    }

    delete[] reqs;
}

void TransferSlot::toggleport(HttpReqXfer *req)
{
    if (!memcmp(req->posturl.c_str(), "http:", 5))
    {
       size_t portendindex = req->posturl.find("/", 8);
       size_t portstartindex = req->posturl.find(":", 8);

       if (portendindex != string::npos)
       {
           if (portstartindex == string::npos)
           {
               LOG_debug << "Enabling alternative port for chunk";
               req->posturl.insert(portendindex, ":8080");
           }
           else
           {
               LOG_debug << "Disabling alternative port for chunk";
               req->posturl.erase(portstartindex, portendindex - portstartindex);
           }
       }
    }
}

// abort all HTTP connections
void TransferSlot::disconnect()
{
    for (int i = connections; i--;)
    {
        if (reqs[i])
        {
            reqs[i]->disconnect();
        }
    }
}

// coalesce block macs into file mac
int64_t TransferSlot::macsmac(chunkmac_map* macs)
{
    byte mac[SymmCipher::BLOCKSIZE] = { 0 };

    for (chunkmac_map::iterator it = macs->begin(); it != macs->end(); it++)
    {
        SymmCipher::xorblock(it->second.mac, mac);
        transfer->key.ecb_encrypt(mac);
    }

    uint32_t* m = (uint32_t*)mac;

    m[0] ^= m[1];
    m[1] = m[2] ^ m[3];

    return MemAccess::get<int64_t>((const char*)mac);
}

// file transfer state machine
void TransferSlot::doio(MegaClient* client)
{
    if (!fa || (transfer->size && transfer->progresscompleted == transfer->size))
    {
        // this is a pending completion, retry every 200 ms by default
        retrybt.backoff(2);
        retrying = true;

        return transfer->complete();
    }

    retrying = false;

    if (!tempurl.size())
    {
        return;
    }

    dstime backoff = 0;
    m_off_t p = 0;

    if (errorcount > 4)
    {
        return transfer->failed(API_EFAILED);
    }

    for (int i = connections; i--; )
    {
        if (reqs[i])
        {
            switch (reqs[i]->status)
            {
                case REQ_INFLIGHT:
                    p += reqs[i]->transferred(client);
                    break;

                case REQ_SUCCESS:
                    lastdata = Waiter::ds;
                    transfer->lastaccesstime = time(NULL);
                    transfer->progresscompleted += reqs[i]->size;

                    LOG_debug << "Chunk finished OK (" << transfer->type << ") Pos: " << transfer->pos
                              << " Completed: " << transfer->progresscompleted << " of " << transfer->size;

                    if (transfer->type == PUT)
                    {
                        errorcount = 0;
                        transfer->failcount = 0;
                        transfer->chunkmacs[reqs[i]->pos].finished = true;

                        // completed put transfers are signalled through the
                        // return of the upload token
                        if (reqs[i]->in.size())
                        {
                            if (reqs[i]->in.size() == NewNode::UPLOADTOKENLEN * 4 / 3)
                            {
                                LOG_debug << "Upload token received";
                                if (Base64::atob(reqs[i]->in.data(), transfer->ultoken, NewNode::UPLOADTOKENLEN + 1)
                                    == NewNode::UPLOADTOKENLEN)
                                {
                                    memcpy(transfer->filekey, transfer->key.key, sizeof transfer->key.key);
                                    ((int64_t*)transfer->filekey)[2] = transfer->ctriv;
                                    ((int64_t*)transfer->filekey)[3] = macsmac(&transfer->chunkmacs);
                                    SymmCipher::xorblock(transfer->filekey + SymmCipher::KEYLENGTH, transfer->filekey);

                                    client->transfercacheadd(transfer);
                                    return transfer->complete();
                                }
                            }

                            LOG_debug << "Invalid upload token: " << reqs[i]->in;
                            transfer->progresscompleted -= reqs[i]->size;
                            transfer->chunkmacs[reqs[i]->pos].finished = false;

                            // fail with returned error
                            return transfer->failed((error)atoi(reqs[i]->in.c_str()));
                        }
                    }
                    else
                    {
                        if (reqs[i]->size == reqs[i]->bufpos)
                        {
                            errorcount = 0;
                            transfer->failcount = 0;

                            reqs[i]->finalize(fa, &transfer->key, &transfer->chunkmacs, transfer->ctriv, 0, -1);

                            if (transfer->progresscompleted == transfer->size)
                            {
                                // verify meta MAC
                                if (!transfer->progresscompleted || (macsmac(&transfer->chunkmacs) == transfer->metamac))
                                {
                                    client->transfercacheadd(transfer);
                                    return transfer->complete();
                                }
                                else
                                {
                                    LOG_warn << "MAC verification failed";
                                    transfer->chunkmacs.clear();
                                    transfer->progresscompleted -= reqs[i]->size;
                                    return transfer->failed(API_EKEY);
                                }
                            }
                        }
                        else
                        {
                            LOG_warn << "Invalid chunk size: " << reqs[i]->size << " - " << reqs[i]->bufpos;
                            transfer->progresscompleted -= reqs[i]->size;
                            errorcount++;
                            reqs[i]->status = REQ_PREPARED;
                            break;
                        }
                    }

                    client->transfercacheadd(transfer);
                    reqs[i]->status = REQ_READY;
                    break;

                case REQ_FAILURE:
                    LOG_warn << "Failed chunk. HTTP status: " << reqs[i]->httpstatus;
                    if (reqs[i]->httpstatus == 509)
                    {
                        if (reqs[i]->timeleft < 0)
                        {
                            int creqtag = client->reqtag;
                            client->reqtag = 0;
                            client->sendevent(99408, "Overquota without timeleft");
                            client->reqtag = creqtag;
                        }

                        LOG_warn << "Bandwidth overquota from storage server";
                        if (reqs[i]->timeleft > 0)
                        {
                            backoff = reqs[i]->timeleft * 10;
                        }
                        else
                        {
                            // default retry intervals
                            backoff = MegaClient::DEFAULT_BW_OVERQUOTA_BACKOFF_SECS * 10;
                        }

                        return transfer->failed(API_EOVERQUOTA, backoff);
                    }
                    else if (reqs[i]->httpstatus == 403 || reqs[i]->httpstatus == 404)
                    {
                        return transfer->failed(API_EAGAIN);
                    }
                    else
                    {
                        if (!failure)
                        {
                            failure = true;
                            bool changeport = false;

                            if (transfer->type == GET && client->autodownport && !memcmp(tempurl.c_str(), "http:", 5))
                            {
                                LOG_debug << "Automatically changing download port";
                                client->usealtdownport = !client->usealtdownport;
                                changeport = true;
                            }
                            else if (transfer->type == PUT && client->autoupport && !memcmp(tempurl.c_str(), "http:", 5))
                            {
                                LOG_debug << "Automatically changing upload port";
                                client->usealtupport = !client->usealtupport;
                                changeport = true;
                            }

                            client->app->transfer_failed(transfer, API_EFAILED);
                            client->setchunkfailed(&reqs[i]->posturl);

                            if (changeport)
                            {
                                toggleport(reqs[i]);
                            }
                        }
                    }
                    reqs[i]->status = REQ_PREPARED;

                default:
                    ;
            }
        }

        if (!failure)
        {
            if (!reqs[i] || (reqs[i]->status == REQ_READY))
            {
                m_off_t npos = ChunkedHash::chunkceil(transfer->nextpos());

                if (npos > transfer->size)
                {
                    npos = transfer->size;
                }

                if (!transfer->size)
                {
                    transfer->pos = 0;
                }

                if ((npos > transfer->pos) || !transfer->size)
                {
                    if (!reqs[i])
                    {
                        reqs[i] = transfer->type == PUT ? (HttpReqXfer*)new HttpReqUL() : (HttpReqXfer*)new HttpReqDL();
                    }

                    string finaltempurl = tempurl;
                    if (transfer->type == GET && client->usealtdownport
                            && !memcmp(tempurl.c_str(), "http:", 5))
                    {
                        size_t index = tempurl.find("/", 8);
                        if(index != string::npos && tempurl.find(":", 8) == string::npos)
                        {
                            finaltempurl.insert(index, ":8080");
                        }
                    }

                    if (transfer->type == PUT && client->usealtupport
                            && !memcmp(tempurl.c_str(), "http:", 5))
                    {
                        size_t index = tempurl.find("/", 8);
                        if(index != string::npos && tempurl.find(":", 8) == string::npos)
                        {
                            finaltempurl.insert(index, ":8080");
                        }
                    }

                    if (reqs[i]->prepare(fa, finaltempurl.c_str(), &transfer->key,
                                         &transfer->chunkmacs, transfer->ctriv,
                                         transfer->pos, npos))
                    {
                        reqs[i]->pos = ChunkedHash::chunkfloor(transfer->pos);
                        reqs[i]->status = REQ_PREPARED;
                        transfer->pos = npos;
                    }
                    else
                    {
                        LOG_warn << "Error preparing transfer: " << fa->retry;
                        if (!fa->retry)
                        {
                            return transfer->failed(API_EREAD);
                        }

                        // retry the read shortly
                        backoff = 2;
                    }
                }
                else if (reqs[i])
                {
                    reqs[i]->status = REQ_DONE;
                }
            }

            if (reqs[i] && (reqs[i]->status == REQ_PREPARED))
            {
                reqs[i]->post(client);
            }
        }
    }

    p += transfer->progresscompleted;

    if (p != progressreported)
    {
        progressreported = p;
        lastdata = Waiter::ds;

        progress();
    }

    if (Waiter::ds - lastdata >= XFERTIMEOUT && !failure)
    {
        LOG_warn << "Failed chunk due to a timeout";
        failure = true;
        bool changeport = false;

        if (transfer->type == GET && client->autodownport && !memcmp(tempurl.c_str(), "http:", 5))
        {
            LOG_debug << "Automatically changing download port due to a timeout";
            client->usealtdownport = !client->usealtdownport;
            changeport = true;
        }
        else if (transfer->type == PUT && client->autoupport && !memcmp(tempurl.c_str(), "http:", 5))
        {
            LOG_debug << "Automatically changing upload port due to a timeout";
            client->usealtupport = !client->usealtupport;
            changeport = true;
        }

        bool chunkfailed = false;
        for (int i = connections; i--; )
        {
            if (reqs[i] && reqs[i]->status == REQ_INFLIGHT)
            {
                chunkfailed = true;
                client->setchunkfailed(&reqs[i]->posturl);
                reqs[i]->disconnect();

                if (changeport)
                {
                    toggleport(reqs[i]);
                }

                reqs[i]->status = REQ_PREPARED;
            }
        }

        if (!chunkfailed)
        {
            LOG_warn << "Transfer failed due to a timeout";
            transfer->failed(API_EAGAIN);
        }
        else
        {
            LOG_warn << "Chunk failed due to a timeout";
            client->app->transfer_failed(transfer, API_EFAILED);
        }
    }

    if (!failure)
    {
        if (!backoff && (Waiter::ds - lastdata) < XFERTIMEOUT)
        {
            // no other backoff: check again at XFERMAXFAIL
            backoff = XFERTIMEOUT - (Waiter::ds - lastdata);
        }

        retrybt.backoff(backoff);
    }
}

// transfer progress notification to app and related files
void TransferSlot::progress()
{
    transfer->client->app->transfer_update(transfer);

    for (file_list::iterator it = transfer->files.begin(); it != transfer->files.end(); it++)
    {
        (*it)->progress();
    }
}
} // namespace
<|endoftext|>


<commit_before># include <Siv3D.hpp> // OpenSiv3D v0.1.5

#include <functional>
#include <shared_mutex>
#include <thread>

#include "entity_component_system/entity_component_system.hpp"

namespace ecs = entity_component_system;

namespace {

template <class T>
class thread_safe : public T {
public:
	using T::T;

	using mutex_type = std::shared_mutex;

	mutex_type &mutex() { return _mutex; }

private:
	mutex_type _mutex;
};

using lock_guard = std::lock_guard<std::shared_mutex>;
using shared_lock = std::shared_lock<std::shared_mutex>;

template <typename... Args>
using system = thread_safe<ecs::system<Args...>>;

using circle_system = system<Circle>;

struct circle_component {
	enum index : size_t {
		entity,
		circle,
	};
};

using color_system = system<HSV>;

struct color_component {
	enum index : size_t {
		entity,
		color,
	};
};

using move_system = system<Vec2>;

struct move_component {
	enum index : size_t {
		entity,
		velocity,
	};
};

using gravity_system = system<>;

struct gravity_component {
	enum index : size_t {
		entity,
	};
};

using color_transition_system = system<double>;

struct color_transition_component {
	enum index : size_t {
		entity,
		hue,
	};
};

struct life_t {
	life_t(double value) : current_life(value), initial_life(value) {}
	life_t() : life_t(0) {}
	double current_life;
	double initial_life;
};

using life_transition_system = system<life_t>;

struct life_component {
	enum index : size_t {
		entity,
		life,
	};
};

using World = thread_safe<ecs::world<
	circle_system,
	color_system,
	move_system,
	color_transition_system,
	life_transition_system,
	gravity_system
>>;

enum world_system : size_t {
	circle,
	color,
	move,
	color_transition,
	life,
	gravity
};

void resetBalls(World &world, int num = 1) {
	lock_guard lock(world.mutex());
	lock_guard lock2(world.get_system<world_system::circle>().mutex());
	lock_guard lock3(world.get_system<world_system::color>().mutex());
	lock_guard lock4(world.get_system<world_system::move>().mutex());
	lock_guard lock5(world.get_system<world_system::color_transition>().mutex());
	lock_guard lock6(world.get_system<world_system::life>().mutex());
	lock_guard lock7(world.get_system<world_system::gravity>().mutex());

	world.clear();

	for (int i = 0; i < num; ++i) {
		auto entity = world.make_entity();
		entity.emplace_component<world_system::circle>(Circle(Vec2(Random(0, Window::Width()), Random(0, Window::Height())), Random(10, 50)));
		entity.emplace_component<world_system::color>(RandomHSV());
		entity.emplace_component<world_system::move>(RandomVec2(Random(10.0, 1000.0)));
		entity.emplace_component<world_system::color_transition>(360.0);
		entity.emplace_component<world_system::gravity>();
		//entity.emplace_component<world_system::life>(Random(1.0, 10.0));
	}
}

void addEffects(World &world, int num = 1) {
	lock_guard lock(world.mutex());
	lock_guard lock2(world.get_system<world_system::circle>().mutex());
	lock_guard lock3(world.get_system<world_system::move>().mutex());
	lock_guard lock4(world.get_system<world_system::color_transition>().mutex());
	lock_guard lock5(world.get_system<world_system::life>().mutex());
	lock_guard lock6(world.get_system<world_system::color>().mutex());
	for (int i = 0; i < num; ++i) {
		auto entity = world.make_entity();
		entity.emplace_component<world_system::circle>(Circle(Cursor::Pos(), Random(1, 10)));
		entity.emplace_component<world_system::color>(RandomHSV());
		entity.emplace_component<world_system::move>(RandomVec2(Random(100.0, 300.0)));
		entity.emplace_component<world_system::color_transition>(360.0);
		entity.emplace_component<world_system::life>(Random(0.1, 0.5));
	}
}

using thread_list = std::vector<std::thread>;

template <class F>
void push_thread(thread_list &threads, F &&f) {
	threads.push_back(std::thread(f));
}

template <class F>
void push_thread(thread_list &threads, F &f) {
	threads.push_back(std::thread(f));
}

} // namespace

void Main()
{
	constexpr int num = 1000;

	World world;

	resetBalls(world, num);

	Graphics::SetBackground(ColorF(0.0, 0.0, 0.0));

	const Font font(50);

	const Texture textureCat(Emoji(L"🐈"), TextureDesc::Mipped);

	std::vector<std::thread> threads;

	while (System::Update())
	{
		Window::SetTitle(Profiler::FPS(), L" FPS : entities=", world.entity_size());

		auto rect = Window::ClientRect();

		push_thread(threads,
			[&rect, &world]() {
				if (rect.leftClicked()) {
					addEffects(world, 10);
				}
			}
		);

		push_thread(threads,
			[&world, &num]() {
				if (KeySpace.down()) {
					resetBalls(world, num);
				}
			}
		);

		push_thread(threads,
			[&]() {
				auto &system = world.get_system<world_system::life>();
				auto &circle_sys = world.get_system<world_system::color>();

				//lock_guard lock(system.mutex());

				auto &entities = system.entities();

				size_t size;
				{
					shared_lock slock(system.mutex());
					size = entities.size();
				}

				auto &lifes = system.get_members<life_component::life>();
				for (size_t i = 0; i < size; ++i) {
					ecs::entity_id entity;
					{
						shared_lock slock(system.mutex());
						entity = entities[i];
					}
					if (entity == ecs::invalid_entity_id) continue;

					{
						lock_guard lock(system.mutex());
						lifes[i].current_life -= System::DeltaTime();
					}
					{
						life_t life;
						{
							shared_lock slock(system.mutex());
							life = lifes[i];
						}
						if (life.current_life < 0) {
							lock_guard lockw(world.mutex());
							world.remove_entity(entity);

						} else if (circle_sys.mutex().try_lock()) {
							if (circle_sys.validate_component(entity)) {
								auto &color = circle_sys.get_member<color_component::color>(entity);
								color.a = life.current_life / life.initial_life;
							}
							circle_sys.mutex().unlock();
						}
					}
				}
			}
		);

		push_thread(threads,
			[&]() {
				auto &system = world.get_system<world_system::color_transition>();
				auto &circle_sys = world.get_system<world_system::color>();
				if (!system.mutex().try_lock_shared()) return;
				//shared_lock slock(system.mutex());
				//shared_lock slock2(circle_sys.mutex());

				auto &entities = system.entities();

				auto &trans = system.get_members<color_transition_component::hue>();
				for (size_t i = 0; i < entities.size(); ++i) {
					ecs::entity_id entity = entities[i];
					if (entity == ecs::invalid_entity_id) continue;

					{
						shared_lock lockc(circle_sys.mutex());
						if (!circle_sys.validate_component(entities[i])) continue;
					}
					if (circle_sys.mutex().try_lock()) {
						//lock_guard lockc(circle_sys.mutex());
						auto &color = circle_sys.get_member<color_component::color>(entities[i]);
						color.h += trans[i] * System::DeltaTime();
						if (color.h <   0) color.h += 360;
						if (color.h > 360) color.h -= 360;
						circle_sys.mutex().unlock();
					}
				}

				system.mutex().unlock_shared();
			}
		);

		push_thread(threads,
			[&]() {
				auto &system = world.get_system<world_system::gravity>();
				auto &move_sys = world.get_system<world_system::move>();
				shared_lock slock(system.mutex());
				//lock_guard lock2(move_sys.mutex());
				if (!move_sys.mutex().try_lock()) return;

				auto &entities = system.entities();

				for (size_t i = 0; i < entities.size(); ++i) {
					if (entities[i] == ecs::invalid_entity_id) continue;

					{
						if (!move_sys.validate_component(entities[i])) continue;
					}

					{
						auto &velocity = move_sys.get_member<move_component::velocity>(entities[i]);
						velocity.y += 9.80665 * System::DeltaTime() * 100;
					}
				}
				move_sys.mutex().unlock();
			}
		);

		push_thread(threads,
			[&]() {
				auto &system = world.get_system<world_system::move>();
				auto &circle_sys = world.get_system<world_system::circle>();

				//lock_guard lock(system.mutex());

				auto &entities = system.entities();
				auto &velocities = system.get_members<move_component::velocity>();

				size_t size;
				{
					shared_lock slock(system.mutex());
					size = entities.size();
				}

				for (size_t i = 0; i < size; ++i) {
					ecs::entity_id entity;
					{
						shared_lock slock(system.mutex());
						entity = entities[i];
					}
					if (entity == ecs::invalid_entity_id) continue;

					{
						shared_lock slock(circle_sys.mutex());
						if (!circle_sys.validate_component(entity)) continue;
					}
					if (system.mutex().try_lock()) {
						//lock_guard locks(system.mutex());
						//lock_guard lockc(circle_sys.mutex());
						if (circle_sys.mutex().try_lock()) {
							auto &center = circle_sys.get_member<circle_component::circle>(entity).center;
							auto &velocity = velocities[i];
							center += velocity * System::DeltaTime();
							constexpr auto cor = 0.9;
							if (center.x < 0) {
								center.x = 0;
								velocity.x = -velocity.x * cor;

							} else if (center.x > rect.w) {
								center.x = rect.w;
								velocity.x = -velocity.x * cor;
							}
							if (center.y < 0) {
								center.y = 0;
								velocity.y = -velocity.y * cor;

							} else if (center.y > rect.h) {
								center.y = rect.h;
								velocity.y = -velocity.y * cor;
							}
							circle_sys.mutex().unlock();
						}
						system.mutex().unlock();
					}
				}
			}
		);

		push_thread(threads,
			[&]() {
				auto &system = world.get_system<world_system::circle>();
				auto &color_sys = world.get_system<world_system::color>();
				shared_lock lock(system.mutex());
				const auto &entities = system.entities();
				const auto &circles = system.get_members<circle_component::circle>();
				for (size_t i = 0; i < circles.size(); ++i) {
					if (entities[i] == ecs::invalid_entity_id) continue;

					HSV color;
					{
						shared_lock lockc(color_sys.mutex());
						color = color_sys.get_member<color_component::color>(entities[i]);
					}
					circles[i].draw(color);
				}
			}
		);

		std::this_thread::yield();

		for (auto &thread : threads) {
			thread.join();
		}
		threads.clear();
#if 0
		font(L"Hello, Siv3D!🐣").drawAt(Window::Center(), Palette::Black);
		font(Cursor::Pos()).draw(20, 400, ColorF(0.6));
		textureCat.resize(80).draw(540, 380);
		Circle(Cursor::Pos(), 60).drawPie(0.5_pi, 1.5_pi, ColorF(1, 0, 0, 0.5));
#endif
	}
}
<commit_msg>マルチスレッド対応<commit_after># include <Siv3D.hpp> // OpenSiv3D v0.1.5

#include <functional>
#include <shared_mutex>
#include <thread>
#include <future>

#include "entity_component_system/entity_component_system.hpp"

namespace ecs = entity_component_system;

namespace {

template <class T>
class thread_safe : public T {
public:
	using T::T;

	using mutex_type = std::shared_mutex;

	mutex_type &mutex() { return _mutex; }

private:
	mutex_type _mutex;
};

using lock_guard = std::lock_guard<std::shared_mutex>;
using shared_lock = std::shared_lock<std::shared_mutex>;

template <typename... Args>
using system = thread_safe<ecs::system<Args...>>;

using circle_system = system<Circle>;

struct circle_component {
	enum index : size_t {
		entity,
		circle,
	};
};

using color_system = system<HSV>;

struct color_component {
	enum index : size_t {
		entity,
		color,
	};
};

using move_system = system<Vec2>;

struct move_component {
	enum index : size_t {
		entity,
		velocity,
	};
};

using gravity_system = system<>;

struct gravity_component {
	enum index : size_t {
		entity,
	};
};

using color_transition_system = system<double>;

struct color_transition_component {
	enum index : size_t {
		entity,
		hue,
	};
};

struct life_t {
	life_t(double value) : current_life(value), initial_life(value) {}
	life_t() : life_t(0) {}
	double current_life;
	double initial_life;
};

using life_transition_system = system<life_t>;

struct life_component {
	enum index : size_t {
		entity,
		life,
	};
};

using World = thread_safe<ecs::world<
	circle_system,
	color_system,
	move_system,
	color_transition_system,
	life_transition_system,
	gravity_system
>>;

enum world_system : size_t {
	circle,
	color,
	move,
	color_transition,
	life,
	gravity
};

void resetBalls(World &world, int num = 1) {
	lock_guard lock(world.mutex());
	lock_guard lock2(world.get_system<world_system::circle>().mutex());
	lock_guard lock3(world.get_system<world_system::color>().mutex());
	lock_guard lock4(world.get_system<world_system::move>().mutex());
	lock_guard lock5(world.get_system<world_system::color_transition>().mutex());
	lock_guard lock6(world.get_system<world_system::life>().mutex());
	lock_guard lock7(world.get_system<world_system::gravity>().mutex());

	world.clear();

	for (int i = 0; i < num; ++i) {
		auto entity = world.make_entity();
		entity.emplace_component<world_system::circle>(Circle(Vec2(Random(0, Window::Width()), Random(0, Window::Height())), Random(10, 50)));
		entity.emplace_component<world_system::color>(RandomHSV());
		entity.emplace_component<world_system::move>(RandomVec2(Random(10.0, 1000.0)));
		entity.emplace_component<world_system::color_transition>(360.0);
		entity.emplace_component<world_system::gravity>();
		//entity.emplace_component<world_system::life>(Random(1.0, 10.0));
	}
}

void addEffects(World &world, int num = 1) {
	lock_guard lock(world.mutex());
	lock_guard lock2(world.get_system<world_system::circle>().mutex());
	lock_guard lock3(world.get_system<world_system::move>().mutex());
	lock_guard lock4(world.get_system<world_system::color_transition>().mutex());
	lock_guard lock5(world.get_system<world_system::life>().mutex());
	lock_guard lock6(world.get_system<world_system::color>().mutex());
	for (int i = 0; i < num; ++i) {
		auto entity = world.make_entity();
		entity.emplace_component<world_system::circle>(Circle(Cursor::Pos(), Random(1, 10)));
		entity.emplace_component<world_system::color>(RandomHSV());
		entity.emplace_component<world_system::move>(RandomVec2(Random(100.0, 300.0)));
		entity.emplace_component<world_system::color_transition>(360.0);
		entity.emplace_component<world_system::life>(Random(0.1, 0.5));
	}
}

using thread_list = std::vector<std::thread>;

template <class T>
struct task {
	using function_type = std::function<T>;

	task(function_type f) : function(f) {}

	std::condition_variable condition;
	function_type function;
};

} // namespace

void Main()
{
	constexpr int num = 1000;

	World world;

	resetBalls(world, num);

	Graphics::SetBackground(ColorF(0.0, 0.0, 0.0));

	const Font font(50);

	const Texture textureCat(Emoji(L"🐈"), TextureDesc::Mipped);

	std::mutex main_mutex;
	bool update = false;
	std::condition_variable cond;
	double delta = 0;

	std::vector<std::future<bool>> futures;
	{
		std::vector<std::packaged_task<bool()>> tasks;

		tasks.emplace_back(
			[&]() {
				while (true) {
					{
						std::unique_lock<std::mutex> main_lock(main_mutex);
						cond.wait(main_lock, [&update] { return update; });
					}

					auto &system = world.get_system<world_system::life>();
					auto &circle_sys = world.get_system<world_system::color>();

					//lock_guard lock(system.mutex());

					auto &entities = system.entities();

					size_t size;
					{
						shared_lock slock(system.mutex());
						size = entities.size();
					}

					auto &lifes = system.get_members<life_component::life>();
					for (size_t i = 0; i < size; ++i) {
						ecs::entity_id entity;
						{
							shared_lock slock(system.mutex());
							entity = entities[i];
						}
						if (entity == ecs::invalid_entity_id) continue;

						{
							lock_guard lock(system.mutex());
							lifes[i].current_life -= delta;
						}
						{
							life_t life;
							{
								shared_lock slock(system.mutex());
								life = lifes[i];
							}
							if (life.current_life < 0) {
								lock_guard lockw(world.mutex());
								world.remove_entity(entity);

							} else if (circle_sys.mutex().try_lock()) {
								if (circle_sys.validate_component(entity)) {
									auto &color = circle_sys.get_member<color_component::color>(entity);
									color.a = life.current_life / life.initial_life;
								}
								circle_sys.mutex().unlock();
							}
						}
					}
				}
				return true;
			}
		);

		tasks.emplace_back(
			[&]() {
				while (true) {
					{
						std::unique_lock<std::mutex> main_lock(main_mutex);
						cond.wait(main_lock, [&update] { return update; });
					}

					auto &system = world.get_system<world_system::color_transition>();
					auto &circle_sys = world.get_system<world_system::color>();
					if (!system.mutex().try_lock_shared()) continue;
					//shared_lock slock(system.mutex());
					//shared_lock slock2(circle_sys.mutex());

					auto &entities = system.entities();

					auto &trans = system.get_members<color_transition_component::hue>();
					for (size_t i = 0; i < entities.size(); ++i) {
						ecs::entity_id entity = entities[i];
						if (entity == ecs::invalid_entity_id) continue;

						{
							shared_lock lockc(circle_sys.mutex());
							if (!circle_sys.validate_component(entities[i])) continue;
						}
						if (circle_sys.mutex().try_lock()) {
							//lock_guard lockc(circle_sys.mutex());
							auto &color = circle_sys.get_member<color_component::color>(entities[i]);
							color.h += trans[i] * delta;
							if (color.h <   0) color.h += 360;
							if (color.h > 360) color.h -= 360;
							circle_sys.mutex().unlock();
						}
					}

					system.mutex().unlock_shared();
				}
				return true;
			}
		);

		tasks.emplace_back(
			[&]() {
				while (true) {
					{
						std::unique_lock<std::mutex> main_lock(main_mutex);
						cond.wait(main_lock, [&update] { return update; });
					}

					auto &system = world.get_system<world_system::gravity>();
					auto &move_sys = world.get_system<world_system::move>();
					shared_lock slock(system.mutex());
					//lock_guard lock2(move_sys.mutex());
					if (!move_sys.mutex().try_lock()) continue;

					auto &entities = system.entities();

					for (size_t i = 0; i < entities.size(); ++i) {
						if (entities[i] == ecs::invalid_entity_id) continue;

						{
							if (!move_sys.validate_component(entities[i])) continue;
						}

						{
							auto &velocity = move_sys.get_member<move_component::velocity>(entities[i]);
							velocity.y += 9.80665 * delta * 100;
						}
					}
					move_sys.mutex().unlock();
				}
				return true;
			}
		);

		tasks.emplace_back(
			[&]() {
				while (true) {
					{
						std::unique_lock<std::mutex> main_lock(main_mutex);
						cond.wait(main_lock, [&update] { return update; });
					}

					auto &system = world.get_system<world_system::move>();
					auto &circle_sys = world.get_system<world_system::circle>();

					//lock_guard lock(system.mutex());

					auto &entities = system.entities();
					auto &velocities = system.get_members<move_component::velocity>();

					size_t size;
					{
						shared_lock slock(system.mutex());
						size = entities.size();
					}

					for (size_t i = 0; i < size; ++i) {
						ecs::entity_id entity;
						{
							shared_lock slock(system.mutex());
							entity = entities[i];
						}
						if (entity == ecs::invalid_entity_id) continue;

						{
							shared_lock slock(circle_sys.mutex());
							if (!circle_sys.validate_component(entity)) continue;
						}
						{
							lock_guard locks(system.mutex());
							lock_guard lockc(circle_sys.mutex());
							{
								auto &center = circle_sys.get_member<circle_component::circle>(entity).center;
								auto &velocity = velocities[i];
								center += velocity * delta;
								constexpr auto cor = 0.9;
								auto rect = Window::ClientRect();
								if (center.x < 0) {
									center.x = 0;
									velocity.x = -velocity.x * cor;

								} else if (center.x > rect.w) {
									center.x = rect.w;
									velocity.x = -velocity.x * cor;
								}
								if (center.y < 0) {
									center.y = 0;
									velocity.y = -velocity.y * cor;

								} else if (center.y > rect.h) {
									center.y = rect.h;
									velocity.y = -velocity.y * cor;
								}
							}
						}
					}
				}
				return true;
			}
		);
#if 0
		tasks.emplace_back(
			[&]() {
				while (true) {
					{
						std::unique_lock<std::mutex> main_lock(main_mutex);
						cond.wait(main_lock, [&update] { return update; });
					}

					auto &system = world.get_system<world_system::circle>();
					auto &color_sys = world.get_system<world_system::color>();
					shared_lock lock(system.mutex());
					const auto &entities = system.entities();
					const auto &circles = system.get_members<circle_component::circle>();
					for (size_t i = 0; i < circles.size(); ++i) {
						if (entities[i] == ecs::invalid_entity_id) continue;

						HSV color;
						{
							shared_lock lockc(color_sys.mutex());
							color = color_sys.get_member<color_component::color>(entities[i]);
						}
						//circles[i].draw(color);
					}
				}
				return true;
			}
		);
#endif
		//std::this_thread::yield();

		std::for_each(
			tasks.begin(),
			tasks.end(),
			[&futures](auto &task) {
				futures.emplace_back(task.get_future());
				std::thread t(std::move(task));
				t.detach();
			}
		);
	}

	while (true)
	{
		{
			std::lock_guard<std::mutex> main_lock(main_mutex);
			if (!System::Update()) break;
			update = true;
			delta = System::DeltaTime();
		}

		Window::SetTitle(Profiler::FPS(), L" FPS : entities=", world.entity_size());

		cond.notify_all();
		{
			std::lock_guard<std::mutex> main_lock(main_mutex);

			auto rect = Window::ClientRect();
			if (rect.leftClicked()) {
				addEffects(world, 10);
			}
			if (KeySpace.down()) {
				resetBalls(world, num);
			}
		}
		{
			std::lock_guard<std::mutex> main_lock(main_mutex);

			auto &system = world.get_system<world_system::circle>();
			auto &color_sys = world.get_system<world_system::color>();
			shared_lock lock(system.mutex());
			const auto &entities = system.entities();
			const auto &circles = system.get_members<circle_component::circle>();
			for (size_t i = 0; i < circles.size(); ++i) {
				if (entities[i] == ecs::invalid_entity_id) continue;

				HSV color;
				{
					shared_lock lockc(color_sys.mutex());
					color = color_sys.get_member<color_component::color>(entities[i]);
				}
				circles[i].draw(color);
			}
		}

		{
			std::lock_guard<std::mutex> main_lock(main_mutex);
			update = false;
		}
#if 0
		font(L"Hello, Siv3D!🐣").drawAt(Window::Center(), Palette::Black);
		font(Cursor::Pos()).draw(20, 400, ColorF(0.6));
		textureCat.resize(80).draw(540, 380);
		Circle(Cursor::Pos(), 60).drawPie(0.5_pi, 1.5_pi, ColorF(1, 0, 0, 0.5));
#endif
	}
}
<|endoftext|>


<commit_before>/* Copyright (c) 2014, Fengping Bao <jamol@live.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "kmtimer.h"

int find_first_set(unsigned int b);
TICK_COUNT_TYPE get_tick_count_ms();
TICK_COUNT_TYPE calc_time_elapse_delta_ms(TICK_COUNT_TYPE now_tick, TICK_COUNT_TYPE& start_tick);

//////////////////////////////////////////////////////////////////////////
// KM_Timer
using namespace komm;

KM_Timer::KM_Timer(KM_Timer_Manager* mgr, TimerHandler* hdr)
{
    m_timer_mgr = mgr;
    m_handler = hdr;
    m_timer_node.timer = this;
}

KM_Timer::~KM_Timer()
{
    unschedule();
}

bool KM_Timer::schedule(unsigned int time_elapse, bool repeat)
{
    if(m_timer_mgr) {
        return m_timer_mgr->schedule(&m_timer_node, time_elapse, repeat);
    }
    return false;
}

void KM_Timer::unschedule()
{
    if(m_timer_mgr) {
        m_timer_mgr->unschedule(&m_timer_node);
    }
}

void KM_Timer::on_timer()
{
    if(m_handler) {
        m_handler->onTimer();
    }
}

//////////////////////////////////////////////////////////////////////////
// KM_Timer_Manager
KM_Timer_Manager::KM_Timer_Manager()
{
    m_running_node = NULL;
    m_reschedule_node = NULL;
    m_timer_count = 0;
    m_last_tick = 0;
    memset(&m_tv0_bitmap, 0, sizeof(m_tv0_bitmap));
    for (int i=0; i<TV_COUNT; ++i)
    {
        for (int j=0; j<TIMER_VECTOR_SIZE; ++j)
        {
            list_init_head(&m_tv[i][j]);
        }
    }
}

KM_Timer_Manager::~KM_Timer_Manager()
{
    KM_Timer_Node* head = NULL;
    KM_Timer_Node* node = NULL;
    m_mutex.lock();
    for(int i=0; i<TV_COUNT; ++i)
    {
        for (int j=0; j<TIMER_VECTOR_SIZE; ++j)
        {
            head = &m_tv[i][j];
            node = head->next;
            while(node != head)
            {
                if(node->timer) {
                    node->timer->on_detach();
                }
                node = node->next;
            }
        }
    }
    m_mutex.unlock();
}

bool KM_Timer_Manager::schedule(KM_Timer_Node* timer_node, unsigned int time_elapse, bool repeat)
{
    if(timer_pending(timer_node) && time_elapse == timer_node->elapse) {
        return true;
    }
    TICK_COUNT_TYPE now_tick = get_tick_count_ms();
    timer_node->unscheduled = false;
    m_mutex.lock();
    if(timer_pending(timer_node)) {
        remove_timer(timer_node);
    }
    timer_node->start_tick = now_tick;
    timer_node->elapse = time_elapse;
    timer_node->repeat = repeat;

    bool ret = add_timer(timer_node, true);
    m_mutex.unlock();
    return ret;
}

void KM_Timer_Manager::unschedule(KM_Timer_Node* timer_node)
{
    if(NULL == timer_node || timer_node->unscheduled) {
        return ;
    }
    timer_node->unscheduled = true;
    bool cancelled = false;
    if(m_reschedule_node == timer_node || timer_pending(timer_node)) {
        m_mutex.lock();
        if(timer_pending(timer_node)) {
            remove_timer(timer_node);
            cancelled = true;
        } else {
            if(m_running_node != timer_node) {
                cancelled = true;
            }
            if(m_reschedule_node == timer_node) {
                m_reschedule_node = NULL;
            }
        }
        m_mutex.unlock();
    }
    // check timer running
    if(!cancelled && m_running_node == timer_node) { // may be running, wait it
        m_running_mutex.lock();
        if(m_running_node == timer_node) {
            m_running_node = NULL;
            cancelled = true;
        }
        m_running_mutex.unlock();
    }
    // check again for reschedule timer
    if(!cancelled && (m_reschedule_node == timer_node || timer_pending(timer_node))) {
        m_mutex.lock();
        if(timer_pending(timer_node)) {
            remove_timer(timer_node);
        }
        if(m_reschedule_node == timer_node) {
            m_reschedule_node = NULL;
        }
        m_mutex.unlock();
    }
}

void KM_Timer_Manager::list_init_head(KM_Timer_Node* head)
{
    head->next = head;
    head->prev = head;
}

void KM_Timer_Manager::list_add_node(KM_Timer_Node* head, KM_Timer_Node* timer_node)
{
    head->prev->next = timer_node;
    timer_node->prev = head->prev;
    timer_node->next = head;
    head->prev = timer_node;
}

void KM_Timer_Manager::list_remove_node(KM_Timer_Node* timer_node)
{
    timer_node->prev->next = timer_node->next;
    timer_node->next->prev = timer_node->prev;
    timer_node->reset();
}

void KM_Timer_Manager::list_replace(KM_Timer_Node* old_head, KM_Timer_Node* new_head)
{
    new_head->next = old_head->next;
    new_head->next->prev = new_head;
    new_head->prev = old_head->prev;
    new_head->prev->next = new_head;
    list_init_head(old_head);
}

void KM_Timer_Manager::list_combine(KM_Timer_Node* from_head, KM_Timer_Node* to_head)
{
    if(from_head->next == from_head) {
        return ;
    }
    to_head->prev->next = from_head->next;
    from_head->next->prev = to_head->prev;
    from_head->prev->next = to_head;
    to_head->prev = from_head->prev;
    list_init_head(from_head);
}

bool KM_Timer_Manager::list_empty(KM_Timer_Node* head)
{
    return head->next == head;
}

void KM_Timer_Manager::set_tv0_bitmap(unsigned char idx)
{
    unsigned char a = idx/(sizeof(m_tv0_bitmap[0])*8);
    unsigned char b = idx%(sizeof(m_tv0_bitmap[0])*8);
    m_tv0_bitmap[a] |= 1 << b;
}

void KM_Timer_Manager::clear_tv0_bitmap(unsigned char idx)
{
    unsigned char a = idx/(sizeof(m_tv0_bitmap[0])*8);
    unsigned char b = idx%(sizeof(m_tv0_bitmap[0])*8);
    m_tv0_bitmap[a] &= ~(1 << b);
}

int KM_Timer_Manager::find_first_set_in_bitmap(unsigned char idx)
{
    unsigned char a = idx/(sizeof(m_tv0_bitmap[0])*8);
    unsigned char b = idx%(sizeof(m_tv0_bitmap[0])*8);
    int pos = -1;
    pos = find_first_set(m_tv0_bitmap[a] >> b);
    if(-1 == pos) {
        int i = a + 1;
        for (i &= 7; i != a; ++i, i &= 7) {
            pos = find_first_set(m_tv0_bitmap[i]);
            if(-1 == pos) {
                continue;
            }
            if(i < a) {
                i += 8;
            }
            pos += (i - a - 1) * 32;
            pos += 32 - b;
            break;
        }
    }
    if(-1 == pos && b > 0) {
        unsigned int bits = (m_tv0_bitmap[a] << (32 - b)) >> (32 - b);
        pos = find_first_set(bits);
        if(pos >= 0) {
            pos += 256 - b;
        }
    }
    return pos;
}

bool KM_Timer_Manager::add_timer(KM_Timer_Node* timer_node, bool from_schedule)
{
    if(0 == m_timer_count) {
        m_last_tick = timer_node->start_tick;
    }
    TICK_COUNT_TYPE fire_tick = timer_node->elapse + timer_node->start_tick;
    if(fire_tick - m_last_tick > (((TICK_COUNT_TYPE)-1)>>1)) // time backward
    {// fire it right now
        fire_tick = m_last_tick;
    }
    if(fire_tick == m_last_tick) {
        ++fire_tick; // = next_jiffies
    }
    TICK_COUNT_TYPE elapse_jiffies = fire_tick - m_last_tick;
    TICK_COUNT_TYPE fire_jiffies = fire_tick;
    KM_Timer_Node* head;
    if(elapse_jiffies < TIMER_VECTOR_SIZE) {
        int i = fire_jiffies & TIMER_VECTOR_MASK;
        head = &m_tv[0][i];
        set_tv0_bitmap(i);
        timer_node->tv_index = 0;
        timer_node->tl_index = i;
    }
    else if(elapse_jiffies < 1 << (2*TIMER_VECTOR_BITS)) {
        int i = (fire_jiffies>>TIMER_VECTOR_BITS) & TIMER_VECTOR_MASK;
        head = &m_tv[1][i];
        timer_node->tv_index = 1;
        timer_node->tl_index = i;
    }
    else if(elapse_jiffies < 1 << (3*TIMER_VECTOR_BITS)) {
        int i = (fire_jiffies>>(2*TIMER_VECTOR_BITS)) & TIMER_VECTOR_MASK;
        head = &m_tv[2][i];
        timer_node->tv_index = 2;
        timer_node->tl_index = i;
    }
    else if(elapse_jiffies <= 0xFFFFFFFF) {
        int i = (fire_jiffies>>(3*TIMER_VECTOR_BITS)) & TIMER_VECTOR_MASK;
        head = &m_tv[3][i];
        timer_node->tv_index = 3;
        timer_node->tl_index = i;
    }
    else
    {// don't support elapse larger than 0xffffffff
        //printf("add_timer, failed, elapse=%lu, start_tick=%lu, last_tick=%lu\n", timer_node->elapse, timer_node->start_tick, m_last_tick);
        return false;
    }
    list_add_node(head, timer_node);
    if(from_schedule) {
        ++m_timer_count;
    }
    return true;
}

void KM_Timer_Manager::remove_timer(KM_Timer_Node* timer_node)
{
    if(0 == timer_node->tv_index
       && timer_node->next != timer_node
       && timer_node->next == timer_node->prev
       && timer_node->next == &m_tv[0][timer_node->tl_index]) {
        clear_tv0_bitmap(timer_node->tl_index);
    }
    list_remove_node(timer_node);
    --m_timer_count;
}

int KM_Timer_Manager::cascade_timer(int tv_idx, int tl_idx)
{
    KM_Timer_Node tmp_head;
    list_init_head(&tmp_head);
    list_replace(&m_tv[tv_idx][tl_idx], &tmp_head);
    KM_Timer_Node* next_node = tmp_head.next;
    KM_Timer_Node* tmp_node = NULL;
    while(next_node != &tmp_head)
    {
        tmp_node = next_node;
        next_node = next_node->next;
        add_timer(tmp_node);
    }

    return tl_idx;
}

#define INDEX(N) ((next_jiffies >> ((N+1) * TIMER_VECTOR_BITS)) & TIMER_VECTOR_MASK)
int KM_Timer_Manager::check_expire(unsigned long* remain_time_ms)
{
    if(0 == m_timer_count) {
        return 0;
    }
    TICK_COUNT_TYPE now_tick = get_tick_count_ms();
    TICK_COUNT_TYPE delta_tick = calc_time_elapse_delta_ms(now_tick, m_last_tick);
    if(0 == delta_tick) {
        return 0;
    }
    TICK_COUNT_TYPE cur_jiffies = now_tick;
    TICK_COUNT_TYPE next_jiffies = m_last_tick+1;
    m_last_tick = now_tick;
    KM_Timer_Node tmp_head;
    list_init_head(&tmp_head);

    m_mutex.lock();
    while(cur_jiffies >= next_jiffies)
    {
        int idx = next_jiffies & TIMER_VECTOR_MASK;
        if (!idx &&
            (!cascade_timer(1, INDEX(0))) &&
            (!cascade_timer(2, INDEX(1)))) {
            cascade_timer(3, INDEX(2));
        }
        ++next_jiffies;
        list_combine(&m_tv[0][idx], &tmp_head);
        clear_tv0_bitmap(idx);
    }
    int count = 0;
    while(!list_empty(&tmp_head))
    {
        m_running_node = tmp_head.next;
        if(!m_running_node->unscheduled && m_running_node->repeat) {
            m_reschedule_node = m_running_node;
        }
        list_remove_node(m_running_node);
        --m_timer_count;
        m_mutex.unlock(); // sync nodes in tmp_list with unschedule.

        m_running_mutex.lock();
        if(m_running_node) {
            if(m_running_node->timer) {
                m_running_node->timer->on_timer();
                ++count;
            }
        } else { // this timer is unscheduled
            m_reschedule_node = NULL;
        }
        m_running_node = NULL;
        m_running_mutex.unlock();

        m_mutex.lock();
        if(m_reschedule_node && !m_reschedule_node->unscheduled && !timer_pending(m_reschedule_node)) {
            m_reschedule_node->start_tick = get_tick_count_ms();
            add_timer(m_reschedule_node, true);
            m_reschedule_node = NULL;
        }
    }

    if(remain_time_ms) {
        // calc remain time in ms
        int pos = find_first_set_in_bitmap(next_jiffies & TIMER_VECTOR_MASK);
        *remain_time_ms = -1==pos?256:pos;

        now_tick = get_tick_count_ms();
        delta_tick = calc_time_elapse_delta_ms(now_tick, m_last_tick);
        if(*remain_time_ms <= delta_tick) {
            *remain_time_ms = 0;
        } else {
            *remain_time_ms -= delta_tick;
        }
    }

    m_mutex.unlock();

    return count;
}

<commit_msg>reset m_reschedule_node in schedule<commit_after>/* Copyright (c) 2014, Fengping Bao <jamol@live.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "kmtimer.h"

int find_first_set(unsigned int b);
TICK_COUNT_TYPE get_tick_count_ms();
TICK_COUNT_TYPE calc_time_elapse_delta_ms(TICK_COUNT_TYPE now_tick, TICK_COUNT_TYPE& start_tick);

//////////////////////////////////////////////////////////////////////////
// KM_Timer
using namespace komm;

KM_Timer::KM_Timer(KM_Timer_Manager* mgr, TimerHandler* hdr)
{
    m_timer_mgr = mgr;
    m_handler = hdr;
    m_timer_node.timer = this;
}

KM_Timer::~KM_Timer()
{
    unschedule();
}

bool KM_Timer::schedule(unsigned int time_elapse, bool repeat)
{
    if(m_timer_mgr) {
        return m_timer_mgr->schedule(&m_timer_node, time_elapse, repeat);
    }
    return false;
}

void KM_Timer::unschedule()
{
    if(m_timer_mgr) {
        m_timer_mgr->unschedule(&m_timer_node);
    }
}

void KM_Timer::on_timer()
{
    if(m_handler) {
        m_handler->onTimer();
    }
}

//////////////////////////////////////////////////////////////////////////
// KM_Timer_Manager
KM_Timer_Manager::KM_Timer_Manager()
{
    m_running_node = NULL;
    m_reschedule_node = NULL;
    m_timer_count = 0;
    m_last_tick = 0;
    memset(&m_tv0_bitmap, 0, sizeof(m_tv0_bitmap));
    for (int i=0; i<TV_COUNT; ++i)
    {
        for (int j=0; j<TIMER_VECTOR_SIZE; ++j)
        {
            list_init_head(&m_tv[i][j]);
        }
    }
}

KM_Timer_Manager::~KM_Timer_Manager()
{
    KM_Timer_Node* head = NULL;
    KM_Timer_Node* node = NULL;
    m_mutex.lock();
    for(int i=0; i<TV_COUNT; ++i)
    {
        for (int j=0; j<TIMER_VECTOR_SIZE; ++j)
        {
            head = &m_tv[i][j];
            node = head->next;
            while(node != head)
            {
                if(node->timer) {
                    node->timer->on_detach();
                }
                node = node->next;
            }
        }
    }
    m_mutex.unlock();
}

bool KM_Timer_Manager::schedule(KM_Timer_Node* timer_node, unsigned int time_elapse, bool repeat)
{
    if(timer_pending(timer_node) && time_elapse == timer_node->elapse) {
        return true;
    }
    TICK_COUNT_TYPE now_tick = get_tick_count_ms();
    timer_node->unscheduled = false;
    m_mutex.lock();
    if(timer_pending(timer_node)) {
        remove_timer(timer_node);
    }
    timer_node->start_tick = now_tick;
    timer_node->elapse = time_elapse;
    timer_node->repeat = repeat;

    bool ret = add_timer(timer_node, true);
    if(m_reschedule_node == timer_node) {
        m_reschedule_node = NULL;
    }
    m_mutex.unlock();
    return ret;
}

void KM_Timer_Manager::unschedule(KM_Timer_Node* timer_node)
{
    if(NULL == timer_node || timer_node->unscheduled) {
        return ;
    }
    timer_node->unscheduled = true;
    bool cancelled = false;
    if(m_reschedule_node == timer_node || timer_pending(timer_node)) {
        m_mutex.lock();
        if(timer_pending(timer_node)) {
            remove_timer(timer_node);
            cancelled = true;
        } else {
            if(m_running_node != timer_node) {
                cancelled = true;
            }
            if(m_reschedule_node == timer_node) {
                m_reschedule_node = NULL;
            }
        }
        m_mutex.unlock();
    }
    // check timer running
    if(!cancelled && m_running_node == timer_node) { // may be running, wait it
        m_running_mutex.lock();
        if(m_running_node == timer_node) {
            m_running_node = NULL;
            cancelled = true;
        }
        m_running_mutex.unlock();
    }
    // check again for reschedule timer
    if(!cancelled && (m_reschedule_node == timer_node || timer_pending(timer_node))) {
        m_mutex.lock();
        if(timer_pending(timer_node)) {
            remove_timer(timer_node);
        }
        if(m_reschedule_node == timer_node) {
            m_reschedule_node = NULL;
        }
        m_mutex.unlock();
    }
}

void KM_Timer_Manager::list_init_head(KM_Timer_Node* head)
{
    head->next = head;
    head->prev = head;
}

void KM_Timer_Manager::list_add_node(KM_Timer_Node* head, KM_Timer_Node* timer_node)
{
    head->prev->next = timer_node;
    timer_node->prev = head->prev;
    timer_node->next = head;
    head->prev = timer_node;
}

void KM_Timer_Manager::list_remove_node(KM_Timer_Node* timer_node)
{
    timer_node->prev->next = timer_node->next;
    timer_node->next->prev = timer_node->prev;
    timer_node->reset();
}

void KM_Timer_Manager::list_replace(KM_Timer_Node* old_head, KM_Timer_Node* new_head)
{
    new_head->next = old_head->next;
    new_head->next->prev = new_head;
    new_head->prev = old_head->prev;
    new_head->prev->next = new_head;
    list_init_head(old_head);
}

void KM_Timer_Manager::list_combine(KM_Timer_Node* from_head, KM_Timer_Node* to_head)
{
    if(from_head->next == from_head) {
        return ;
    }
    to_head->prev->next = from_head->next;
    from_head->next->prev = to_head->prev;
    from_head->prev->next = to_head;
    to_head->prev = from_head->prev;
    list_init_head(from_head);
}

bool KM_Timer_Manager::list_empty(KM_Timer_Node* head)
{
    return head->next == head;
}

void KM_Timer_Manager::set_tv0_bitmap(unsigned char idx)
{
    unsigned char a = idx/(sizeof(m_tv0_bitmap[0])*8);
    unsigned char b = idx%(sizeof(m_tv0_bitmap[0])*8);
    m_tv0_bitmap[a] |= 1 << b;
}

void KM_Timer_Manager::clear_tv0_bitmap(unsigned char idx)
{
    unsigned char a = idx/(sizeof(m_tv0_bitmap[0])*8);
    unsigned char b = idx%(sizeof(m_tv0_bitmap[0])*8);
    m_tv0_bitmap[a] &= ~(1 << b);
}

int KM_Timer_Manager::find_first_set_in_bitmap(unsigned char idx)
{
    unsigned char a = idx/(sizeof(m_tv0_bitmap[0])*8);
    unsigned char b = idx%(sizeof(m_tv0_bitmap[0])*8);
    int pos = -1;
    pos = find_first_set(m_tv0_bitmap[a] >> b);
    if(-1 == pos) {
        int i = a + 1;
        for (i &= 7; i != a; ++i, i &= 7) {
            pos = find_first_set(m_tv0_bitmap[i]);
            if(-1 == pos) {
                continue;
            }
            if(i < a) {
                i += 8;
            }
            pos += (i - a - 1) * 32;
            pos += 32 - b;
            break;
        }
    }
    if(-1 == pos && b > 0) {
        unsigned int bits = (m_tv0_bitmap[a] << (32 - b)) >> (32 - b);
        pos = find_first_set(bits);
        if(pos >= 0) {
            pos += 256 - b;
        }
    }
    return pos;
}

bool KM_Timer_Manager::add_timer(KM_Timer_Node* timer_node, bool from_schedule)
{
    if(0 == m_timer_count) {
        m_last_tick = timer_node->start_tick;
    }
    TICK_COUNT_TYPE fire_tick = timer_node->elapse + timer_node->start_tick;
    if(fire_tick - m_last_tick > (((TICK_COUNT_TYPE)-1)>>1)) // time backward
    {// fire it right now
        fire_tick = m_last_tick;
    }
    if(fire_tick == m_last_tick) {
        ++fire_tick; // = next_jiffies
    }
    TICK_COUNT_TYPE elapse_jiffies = fire_tick - m_last_tick;
    TICK_COUNT_TYPE fire_jiffies = fire_tick;
    KM_Timer_Node* head;
    if(elapse_jiffies < TIMER_VECTOR_SIZE) {
        int i = fire_jiffies & TIMER_VECTOR_MASK;
        head = &m_tv[0][i];
        set_tv0_bitmap(i);
        timer_node->tv_index = 0;
        timer_node->tl_index = i;
    }
    else if(elapse_jiffies < 1 << (2*TIMER_VECTOR_BITS)) {
        int i = (fire_jiffies>>TIMER_VECTOR_BITS) & TIMER_VECTOR_MASK;
        head = &m_tv[1][i];
        timer_node->tv_index = 1;
        timer_node->tl_index = i;
    }
    else if(elapse_jiffies < 1 << (3*TIMER_VECTOR_BITS)) {
        int i = (fire_jiffies>>(2*TIMER_VECTOR_BITS)) & TIMER_VECTOR_MASK;
        head = &m_tv[2][i];
        timer_node->tv_index = 2;
        timer_node->tl_index = i;
    }
    else if(elapse_jiffies <= 0xFFFFFFFF) {
        int i = (fire_jiffies>>(3*TIMER_VECTOR_BITS)) & TIMER_VECTOR_MASK;
        head = &m_tv[3][i];
        timer_node->tv_index = 3;
        timer_node->tl_index = i;
    }
    else
    {// don't support elapse larger than 0xffffffff
        //printf("add_timer, failed, elapse=%lu, start_tick=%lu, last_tick=%lu\n", timer_node->elapse, timer_node->start_tick, m_last_tick);
        return false;
    }
    list_add_node(head, timer_node);
    if(from_schedule) {
        ++m_timer_count;
    }
    return true;
}

void KM_Timer_Manager::remove_timer(KM_Timer_Node* timer_node)
{
    if(0 == timer_node->tv_index
       && timer_node->next != timer_node
       && timer_node->next == timer_node->prev
       && timer_node->next == &m_tv[0][timer_node->tl_index]) {
        clear_tv0_bitmap(timer_node->tl_index);
    }
    list_remove_node(timer_node);
    --m_timer_count;
}

int KM_Timer_Manager::cascade_timer(int tv_idx, int tl_idx)
{
    KM_Timer_Node tmp_head;
    list_init_head(&tmp_head);
    list_replace(&m_tv[tv_idx][tl_idx], &tmp_head);
    KM_Timer_Node* next_node = tmp_head.next;
    KM_Timer_Node* tmp_node = NULL;
    while(next_node != &tmp_head)
    {
        tmp_node = next_node;
        next_node = next_node->next;
        add_timer(tmp_node);
    }

    return tl_idx;
}

#define INDEX(N) ((next_jiffies >> ((N+1) * TIMER_VECTOR_BITS)) & TIMER_VECTOR_MASK)
int KM_Timer_Manager::check_expire(unsigned long* remain_time_ms)
{
    if(0 == m_timer_count) {
        return 0;
    }
    TICK_COUNT_TYPE now_tick = get_tick_count_ms();
    TICK_COUNT_TYPE delta_tick = calc_time_elapse_delta_ms(now_tick, m_last_tick);
    if(0 == delta_tick) {
        return 0;
    }
    TICK_COUNT_TYPE cur_jiffies = now_tick;
    TICK_COUNT_TYPE next_jiffies = m_last_tick+1;
    m_last_tick = now_tick;
    KM_Timer_Node tmp_head;
    list_init_head(&tmp_head);

    m_mutex.lock();
    while(cur_jiffies >= next_jiffies)
    {
        int idx = next_jiffies & TIMER_VECTOR_MASK;
        if (!idx &&
            (!cascade_timer(1, INDEX(0))) &&
            (!cascade_timer(2, INDEX(1)))) {
            cascade_timer(3, INDEX(2));
        }
        ++next_jiffies;
        list_combine(&m_tv[0][idx], &tmp_head);
        clear_tv0_bitmap(idx);
    }
    int count = 0;
    while(!list_empty(&tmp_head))
    {
        m_running_node = tmp_head.next;
        if(!m_running_node->unscheduled && m_running_node->repeat) {
            m_reschedule_node = m_running_node;
        }
        list_remove_node(m_running_node);
        --m_timer_count;
        m_mutex.unlock(); // sync nodes in tmp_list with unschedule.

        m_running_mutex.lock();
        if(m_running_node) {
            if(m_running_node->timer) {
                m_running_node->timer->on_timer();
                ++count;
            }
        } else { // this timer is unscheduled
            m_reschedule_node = NULL;
        }
        m_running_node = NULL;
        m_running_mutex.unlock();

        m_mutex.lock();
        if(m_reschedule_node && !m_reschedule_node->unscheduled && !timer_pending(m_reschedule_node)) {
            m_reschedule_node->start_tick = get_tick_count_ms();
            add_timer(m_reschedule_node, true);
            m_reschedule_node = NULL;
        }
    }

    if(remain_time_ms) {
        // calc remain time in ms
        int pos = find_first_set_in_bitmap(next_jiffies & TIMER_VECTOR_MASK);
        *remain_time_ms = -1==pos?256:pos;

        now_tick = get_tick_count_ms();
        delta_tick = calc_time_elapse_delta_ms(now_tick, m_last_tick);
        if(*remain_time_ms <= delta_tick) {
            *remain_time_ms = 0;
        } else {
            *remain_time_ms -= delta_tick;
        }
    }

    m_mutex.unlock();

    return count;
}

<|endoftext|>


<commit_before>// __BEGIN_LICENSE__
//  Copyright (c) 2006-2012, United States Government as represented by the
//  Administrator of the National Aeronautics and Space Administration. All
//  rights reserved.
//
//  The NASA Vision Workbench is licensed under the Apache License,
//  Version 2.0 (the "License"); you may not use this file except in
//  compliance with the License. You may obtain a copy of the License at
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// __END_LICENSE__

/// \file Core/Cache.cc
///
/// Types and functions to assist cacheing regeneratable data.
///
#include <vw/Core/Cache.h>
#include <vw/Core/Debugging.h>

void vw::Cache::allocate( size_t size, CacheLineBase* line ) {
  // WARNING! YOU CAN NOT HOLD THE CACHE MUTEX AND THEN CALL
  // INVALIDATE. That's a line -> cache -> line mutex hold. A
  // deadlock!

  // This step also implies the need to call validate.

  uint64 local_evictions = 0;
  size_t local_size, local_max_size;
  CacheLineBase* local_last_valid;
  size_t invalidate_loop_count = 0;
  { // Locally buffer variables that should be accessed behind mutex
    RecursiveMutex::Lock cache_lock( m_line_mgmt_mutex );
    validate( line ); // Call here to insure that last_valid is not us!
    m_size += size;
    local_size = m_size;
    local_max_size = m_max_size;
    local_last_valid = m_last_valid;
    VW_CACHE_DEBUG( VW_OUT(DebugMessage, "cache") << "Cache allocated " << size << " bytes (" << m_size << " / " << m_max_size << " used)" << "\n"; );
  }
  while ( local_size > local_max_size ) {
    if ( local_last_valid == line ) {
      // We're trying to deallocate self! Call validate again to get
      // our line back on top since some other threads have moved us.
      RecursiveMutex::Lock cache_lock( m_line_mgmt_mutex );
      validate( line );
      local_last_valid = m_last_valid;
    }
    if ( local_last_valid == line || !local_last_valid ) {
      VW_OUT(WarningMessage, "console") << "Warning: Cached object (" << size << ") larger than requested maximum cache size (" << m_max_size << "). Current Size = " << m_size << "\n";
      VW_OUT(WarningMessage, "cache") << "Warning: Cached object (" << size << ") larger than requested maximum cache size (" << m_max_size << "). Current Size = " << m_size << "\n";
      break;
    }
    bool invalidated = local_last_valid->try_invalidate();
    if (invalidated) {
      local_evictions++;
      { // Update local buffer by grabbing cache buffer
        RecursiveMutex::Lock cache_lock( m_line_mgmt_mutex );
        local_size = m_size;
        local_last_valid = m_last_valid;
      }
    } else {
      RecursiveMutex::Lock cache_lock( m_line_mgmt_mutex );
      local_last_valid = local_last_valid->m_prev;
      local_size = m_size;
      if (!local_last_valid) {
        invalidate_loop_count++;
        if ( invalidate_loop_count == 3 ) {
          // We tried hard to deallocate enough to do our
          // allocation. However there is a time to give up and just
          // move on. In practice, even with this cop out, we do stay
          // pretty close to our allocations amount.
          break;
        } {
          local_last_valid = m_last_valid;
        }
      }
    }
  }
  {
    Mutex::WriteLock cache_lock( m_stats_mutex );
    m_evictions += local_evictions;
  }
}

void vw::Cache::resize( size_t size ) {
  // WARNING! YOU CAN NOT HOLD THE CACHE MUTEX AND THEN CALL
  // INVALIDATE. That's a line -> cache -> line mutex hold. A
  // deadlock!
  size_t local_size, local_max_size;
  CacheLineBase* local_last_valid;
  { // Locally buffer variables that require Cache Mutex
    RecursiveMutex::Lock cache_lock(m_line_mgmt_mutex);
    m_max_size = size;
    local_size = m_size;
    local_max_size = m_max_size;
    local_last_valid = m_last_valid;
  }
  while ( local_size > local_max_size ) {
    VW_ASSERT( local_last_valid, LogicErr() << "Cache is empty but has nonzero size!" );
    m_last_valid->invalidate(); // Problem ( probably grabs a line's mutex too )
    { // Update local buffer by grabbing cache buffer
      RecursiveMutex::Lock cache_lock( m_line_mgmt_mutex );
      local_size = m_size;
      local_last_valid = m_last_valid;
    }
  }
}

size_t vw::Cache::max_size() {
  RecursiveMutex::Lock cache_lock(m_line_mgmt_mutex);
  return m_max_size;
}

void vw::Cache::deallocate( size_t size, CacheLineBase *line ) {
  RecursiveMutex::Lock cache_lock(m_line_mgmt_mutex);

  // This call implies the need to call invalidate
  invalidate( line );

  m_size -= size;
  VW_CACHE_DEBUG( VW_OUT(DebugMessage, "cache") << "Cache deallocated " << size << " bytes (" << m_size << " / " << m_max_size << " used)" << "\n"; )
}

// Move the cache line to the top of the valid list.
void vw::Cache::validate( CacheLineBase *line ) {
  RecursiveMutex::Lock cache_lock(m_line_mgmt_mutex);
  if( line == m_first_valid ) return;
  if( line == m_last_valid ) m_last_valid = line->m_prev;
  if( line == m_first_invalid ) m_first_invalid = line->m_next;
  if( line->m_next ) line->m_next->m_prev = line->m_prev;
  if( line->m_prev ) line->m_prev->m_next = line->m_next;
  line->m_next = m_first_valid;
  line->m_prev = 0;
  if( m_first_valid ) m_first_valid->m_prev = line;
  m_first_valid = line;
  if( ! m_last_valid ) m_last_valid = line;
}

// Move the cache line to the top of the invalid list.
void vw::Cache::invalidate( CacheLineBase *line ) {
  RecursiveMutex::Lock cache_lock(m_line_mgmt_mutex);
  if( line == m_first_valid ) m_first_valid = line->m_next;
  if( line == m_last_valid ) m_last_valid = line->m_prev;
  if( line->m_next ) line->m_next->m_prev = line->m_prev;
  if( line->m_prev ) line->m_prev->m_next = line->m_next;
  line->m_next = m_first_invalid;
  line->m_prev = 0;
  if( m_first_invalid ) m_first_invalid->m_prev = line;
  m_first_invalid = line;
}

// Remove the cache line from the cache lists.
void vw::Cache::remove( CacheLineBase *line ) {
  RecursiveMutex::Lock cache_lock(m_line_mgmt_mutex);
  if( line == m_first_valid ) m_first_valid = line->m_next;
  if( line == m_last_valid ) m_last_valid = line->m_prev;
  if( line == m_first_invalid ) m_first_invalid = line->m_next;
  if( line->m_next ) line->m_next->m_prev = line->m_prev;
  if( line->m_prev ) line->m_prev->m_next = line->m_next;
  line->m_next = line->m_prev = 0;
}

// Move the cache line to the bottom of the valid list.
void vw::Cache::deprioritize( CacheLineBase *line ) {
  RecursiveMutex::Lock cache_lock(m_line_mgmt_mutex);
  if( line == m_last_valid ) return;
  if( line == m_first_valid ) m_first_valid = line->m_next;
  if( line->m_next ) line->m_next->m_prev = line->m_prev;
  if( line->m_prev ) line->m_prev->m_next = line->m_next;
  line->m_prev = m_last_valid;
  line->m_next = 0;
  m_last_valid->m_next = line;
  m_last_valid = line;
}

// Statistics request methods
vw::uint64 vw::Cache::hits() {
  Mutex::ReadLock cache_lock( m_stats_mutex );
  return m_hits;
}

vw::uint64 vw::Cache::misses() {
  Mutex::ReadLock cache_lock( m_stats_mutex );
  return m_misses;
}

vw::uint64 vw::Cache::evictions() {
  Mutex::ReadLock cache_lock( m_stats_mutex );
  return m_evictions;
}

void vw::Cache::clear_stats() {
  Mutex::WriteLock cache_lock( m_stats_mutex );
  m_hits = m_misses = m_evictions = 0;
}
<commit_msg>Core: Remove extra "Warning:"<commit_after>// __BEGIN_LICENSE__
//  Copyright (c) 2006-2012, United States Government as represented by the
//  Administrator of the National Aeronautics and Space Administration. All
//  rights reserved.
//
//  The NASA Vision Workbench is licensed under the Apache License,
//  Version 2.0 (the "License"); you may not use this file except in
//  compliance with the License. You may obtain a copy of the License at
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// __END_LICENSE__

/// \file Core/Cache.cc
///
/// Types and functions to assist cacheing regeneratable data.
///
#include <vw/Core/Cache.h>
#include <vw/Core/Debugging.h>

void vw::Cache::allocate( size_t size, CacheLineBase* line ) {
  // WARNING! YOU CAN NOT HOLD THE CACHE MUTEX AND THEN CALL
  // INVALIDATE. That's a line -> cache -> line mutex hold. A
  // deadlock!

  // This step also implies the need to call validate.

  uint64 local_evictions = 0;
  size_t local_size, local_max_size;
  CacheLineBase* local_last_valid;
  size_t invalidate_loop_count = 0;
  { // Locally buffer variables that should be accessed behind mutex
    RecursiveMutex::Lock cache_lock( m_line_mgmt_mutex );
    validate( line ); // Call here to insure that last_valid is not us!
    m_size += size;
    local_size = m_size;
    local_max_size = m_max_size;
    local_last_valid = m_last_valid;
    VW_CACHE_DEBUG( VW_OUT(DebugMessage, "cache") << "Cache allocated " << size << " bytes (" << m_size << " / " << m_max_size << " used)" << "\n"; );
  }
  while ( local_size > local_max_size ) {
    if ( local_last_valid == line ) {
      // We're trying to deallocate self! Call validate again to get
      // our line back on top since some other threads have moved us.
      RecursiveMutex::Lock cache_lock( m_line_mgmt_mutex );
      validate( line );
      local_last_valid = m_last_valid;
    }
    if ( local_last_valid == line || !local_last_valid ) {
      VW_OUT(WarningMessage, "console") << "Cached object (" << size << ") larger than requested maximum cache size (" << m_max_size << "). Current Size = " << m_size << "\n";
      VW_OUT(WarningMessage, "cache") << "Cached object (" << size << ") larger than requested maximum cache size (" << m_max_size << "). Current Size = " << m_size << "\n";
      break;
    }
    bool invalidated = local_last_valid->try_invalidate();
    if (invalidated) {
      local_evictions++;
      { // Update local buffer by grabbing cache buffer
        RecursiveMutex::Lock cache_lock( m_line_mgmt_mutex );
        local_size = m_size;
        local_last_valid = m_last_valid;
      }
    } else {
      RecursiveMutex::Lock cache_lock( m_line_mgmt_mutex );
      local_last_valid = local_last_valid->m_prev;
      local_size = m_size;
      if (!local_last_valid) {
        invalidate_loop_count++;
        if ( invalidate_loop_count == 3 ) {
          // We tried hard to deallocate enough to do our
          // allocation. However there is a time to give up and just
          // move on. In practice, even with this cop out, we do stay
          // pretty close to our allocations amount.
          break;
        } {
          local_last_valid = m_last_valid;
        }
      }
    }
  }
  {
    Mutex::WriteLock cache_lock( m_stats_mutex );
    m_evictions += local_evictions;
  }
}

void vw::Cache::resize( size_t size ) {
  // WARNING! YOU CAN NOT HOLD THE CACHE MUTEX AND THEN CALL
  // INVALIDATE. That's a line -> cache -> line mutex hold. A
  // deadlock!
  size_t local_size, local_max_size;
  CacheLineBase* local_last_valid;
  { // Locally buffer variables that require Cache Mutex
    RecursiveMutex::Lock cache_lock(m_line_mgmt_mutex);
    m_max_size = size;
    local_size = m_size;
    local_max_size = m_max_size;
    local_last_valid = m_last_valid;
  }
  while ( local_size > local_max_size ) {
    VW_ASSERT( local_last_valid, LogicErr() << "Cache is empty but has nonzero size!" );
    m_last_valid->invalidate(); // Problem ( probably grabs a line's mutex too )
    { // Update local buffer by grabbing cache buffer
      RecursiveMutex::Lock cache_lock( m_line_mgmt_mutex );
      local_size = m_size;
      local_last_valid = m_last_valid;
    }
  }
}

size_t vw::Cache::max_size() {
  RecursiveMutex::Lock cache_lock(m_line_mgmt_mutex);
  return m_max_size;
}

void vw::Cache::deallocate( size_t size, CacheLineBase *line ) {
  RecursiveMutex::Lock cache_lock(m_line_mgmt_mutex);

  // This call implies the need to call invalidate
  invalidate( line );

  m_size -= size;
  VW_CACHE_DEBUG( VW_OUT(DebugMessage, "cache") << "Cache deallocated " << size << " bytes (" << m_size << " / " << m_max_size << " used)" << "\n"; )
}

// Move the cache line to the top of the valid list.
void vw::Cache::validate( CacheLineBase *line ) {
  RecursiveMutex::Lock cache_lock(m_line_mgmt_mutex);
  if( line == m_first_valid ) return;
  if( line == m_last_valid ) m_last_valid = line->m_prev;
  if( line == m_first_invalid ) m_first_invalid = line->m_next;
  if( line->m_next ) line->m_next->m_prev = line->m_prev;
  if( line->m_prev ) line->m_prev->m_next = line->m_next;
  line->m_next = m_first_valid;
  line->m_prev = 0;
  if( m_first_valid ) m_first_valid->m_prev = line;
  m_first_valid = line;
  if( ! m_last_valid ) m_last_valid = line;
}

// Move the cache line to the top of the invalid list.
void vw::Cache::invalidate( CacheLineBase *line ) {
  RecursiveMutex::Lock cache_lock(m_line_mgmt_mutex);
  if( line == m_first_valid ) m_first_valid = line->m_next;
  if( line == m_last_valid ) m_last_valid = line->m_prev;
  if( line->m_next ) line->m_next->m_prev = line->m_prev;
  if( line->m_prev ) line->m_prev->m_next = line->m_next;
  line->m_next = m_first_invalid;
  line->m_prev = 0;
  if( m_first_invalid ) m_first_invalid->m_prev = line;
  m_first_invalid = line;
}

// Remove the cache line from the cache lists.
void vw::Cache::remove( CacheLineBase *line ) {
  RecursiveMutex::Lock cache_lock(m_line_mgmt_mutex);
  if( line == m_first_valid ) m_first_valid = line->m_next;
  if( line == m_last_valid ) m_last_valid = line->m_prev;
  if( line == m_first_invalid ) m_first_invalid = line->m_next;
  if( line->m_next ) line->m_next->m_prev = line->m_prev;
  if( line->m_prev ) line->m_prev->m_next = line->m_next;
  line->m_next = line->m_prev = 0;
}

// Move the cache line to the bottom of the valid list.
void vw::Cache::deprioritize( CacheLineBase *line ) {
  RecursiveMutex::Lock cache_lock(m_line_mgmt_mutex);
  if( line == m_last_valid ) return;
  if( line == m_first_valid ) m_first_valid = line->m_next;
  if( line->m_next ) line->m_next->m_prev = line->m_prev;
  if( line->m_prev ) line->m_prev->m_next = line->m_next;
  line->m_prev = m_last_valid;
  line->m_next = 0;
  m_last_valid->m_next = line;
  m_last_valid = line;
}

// Statistics request methods
vw::uint64 vw::Cache::hits() {
  Mutex::ReadLock cache_lock( m_stats_mutex );
  return m_hits;
}

vw::uint64 vw::Cache::misses() {
  Mutex::ReadLock cache_lock( m_stats_mutex );
  return m_misses;
}

vw::uint64 vw::Cache::evictions() {
  Mutex::ReadLock cache_lock( m_stats_mutex );
  return m_evictions;
}

void vw::Cache::clear_stats() {
  Mutex::WriteLock cache_lock( m_stats_mutex );
  m_hits = m_misses = m_evictions = 0;
}
<|endoftext|>