<commit_before>/*
* Copyright (c) 2012-2021 Fredrik Mellbin
*
* This file is part of VapourSynth.
*
* VapourSynth is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* VapourSynth is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with VapourSynth; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "vscore.h"
#include <cassert>
#include <bitset>
#ifdef VS_TARGET_CPU_X86
#include "x86utils.h"
#endif

#if defined(HAVE_SCHED_GETAFFINITY)
#include <sched.h>
#elif defined(HAVE_CPUSET_GETAFFINITY)
#include <sys/param.h>
#include <sys/_cpuset.h>
#include <sys/cpuset.h>
#endif

size_t VSThreadPool::getNumAvailableThreads() {
    size_t nthreads = std::thread::hardware_concurrency();
#ifdef _WIN32
    DWORD_PTR pAff = 0;
    DWORD_PTR sAff = 0;
    BOOL res = GetProcessAffinityMask(GetCurrentProcess(), &pAff, &sAff);
    if (res && pAff != 0) {
        std::bitset<sizeof(sAff) * 8> b(pAff);
        nthreads = b.count();
    }
#elif defined(HAVE_SCHED_GETAFFINITY)
    // Linux only.
    cpu_set_t affinity;
    if (sched_getaffinity(0, sizeof(cpu_set_t), &affinity) == 0)
        nthreads = CPU_COUNT(&affinity);
#elif defined(HAVE_CPUSET_GETAFFINITY)
    // BSD only (FreeBSD only?)
    cpuset_t affinity;
    if (cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1, sizeof(cpuset_t), &affinity) == 0)
        nthreads = CPU_COUNT(&affinity);
#endif

    return nthreads;
}

bool VSThreadPool::taskCmp(const PVSFrameContext &a, const PVSFrameContext &b) {
    return (a->reqOrder < b->reqOrder) || (a->reqOrder == b->reqOrder && a->key.second < b->key.second);
}

void VSThreadPool::runTasksWrapper(VSThreadPool *owner, std::atomic<bool> &stop) {
    owner->runTasks(stop);
}

void VSThreadPool::runTasks(std::atomic<bool> &stop) {
#ifdef VS_TARGET_OS_WINDOWS
    if (!vs_isSSEStateOk())
        core->logFatal("Bad SSE state detected after creating new thread");
#endif

    std::unique_lock<std::mutex> lock(taskLock);

    while (true) {
        bool ranTask = false;

/////////////////////////////////////////////////////////////////////////////////////////////
// Go through all tasks from the top (oldest) and process the first one possible

        std::set<VSNode *> seenNodes;

        for (auto iter = tasks.begin(); iter != tasks.end(); ++iter) {
            VSFrameContext *frameContext = iter->get();
            VSNode *node = frameContext->key.first;

/////////////////////////////////////////////////////////////////////////////////////////////
// Fast path if a frame is cached

            if (node->cacheEnabled) {
                PVSFrame f = node->getCachedFrameInternal(frameContext->key.second);

                if (f) {
                    bool needsSort = false;

                    for (size_t i = 0; i < frameContext->notifyCtxList.size(); i++) {
                        PVSFrameContext &notify = frameContext->notifyCtxList[i];
                        notify->availableFrames.push_back({frameContext->key, f});

                        assert(notify->numFrameRequests > 0);
                        if (--notify->numFrameRequests == 0) {
                            queueTask(notify);
                            needsSort = true;
                        }
                    }

                    PVSFrameContext mainContextRef = std::move(*iter);

                    if (frameContext->external)
                        returnFrame(frameContext, f);

                    allContexts.erase(frameContext->key);
                    tasks.erase(iter);

                    if (needsSort)
                        tasks.sort(taskCmp);

                    ranTask = true;
                    break;
                }
            }

/////////////////////////////////////////////////////////////////////////////////////////////
// This part handles the locking for the different filter modes

            int filterMode = node->filterMode;

            // Don't try to lock the same node twice since it's likely to fail and will produce more out of order requests as well
            if (filterMode != fmFrameState && !seenNodes.insert(node).second)
                continue;

            // Does the filter need the per instance mutex? fmFrameState, fmUnordered and fmParallelRequests (when in the arAllFramesReady state) use this
            bool useSerialLock = (filterMode == fmFrameState || filterMode == fmUnordered || (filterMode == fmParallelRequests && !frameContext->first));

            if (useSerialLock) {
                if (!node->serialMutex.try_lock())
                    continue;
                if (filterMode == fmFrameState) {
                    if (node->serialFrame == -1) {
                        node->serialFrame = frameContext->key.second;
                        // another frame already in progress?
                    } else if (node->serialFrame != frameContext->key.second) {
                        node->serialMutex.unlock();
                        continue;
                    }
                }
            }

/////////////////////////////////////////////////////////////////////////////////////////////
// Remove the context from the task list and keep references around until processing is done

            PVSFrameContext frameContextRef = std::move(*iter);
            tasks.erase(iter);

/////////////////////////////////////////////////////////////////////////////////////////////
// Figure out the activation reason

            assert(frameContext->numFrameRequests == 0);
            int ar = arInitial;
            if (frameContext->hasError()) {
                ar = arError;
            } else if (!frameContext->first) {
                ar = (node->apiMajor == 3) ? static_cast<int>(vs3::arAllFramesReady) : static_cast<int>(arAllFramesReady);
            } else if (frameContext->first) {
                frameContext->first = false;
            }

/////////////////////////////////////////////////////////////////////////////////////////////
// Do the actual processing

            lock.unlock();

            PVSFrame f = node->getFrameInternal(frameContext->key.second, ar, frameContext);
            ranTask = true;

            bool frameProcessingDone = f || frameContext->hasError();
            if (frameContext->hasError() && f)
                core->logFatal("A frame was returned by " + node->name + " but an error was also set, this is not allowed");

/////////////////////////////////////////////////////////////////////////////////////////////
// Unlock so the next job can run on the context
            if (useSerialLock) {
                if (frameProcessingDone && filterMode == fmFrameState)
                    node->serialFrame = -1;
                node->serialMutex.unlock();
            }

/////////////////////////////////////////////////////////////////////////////////////////////
// Handle frames that were requested
            bool requestedFrames = frameContext->reqList.size() > 0 && !frameProcessingDone;
            bool needsSort = false;
            if (f && requestedFrames)
                core->logFatal("A frame was returned at the end of processing by " + node->name + " but there are still outstanding requests");

            lock.lock();

            if (requestedFrames) {
                assert(frameContext->numFrameRequests == 0);

                for (size_t i = 0; i < frameContext->reqList.size(); i++)
                    startInternalRequest(frameContextRef, frameContext->reqList[i]);

                frameContext->numFrameRequests = frameContext->reqList.size();
                frameContext->reqList.clear();
            }

            if (frameProcessingDone)
                allContexts.erase(frameContext->key);

/////////////////////////////////////////////////////////////////////////////////////////////
// Notify all dependent contexts

            if (frameContext->hasError()) {
                for (size_t i = 0; i < frameContextRef->notifyCtxList.size(); i++) {
                    PVSFrameContext &notify = frameContextRef->notifyCtxList[i];
                    notify->setError(frameContextRef->getErrorMessage());

                    assert(notify->numFrameRequests > 0);
                    if (--notify->numFrameRequests == 0) {
                        queueTask(notify);
                        needsSort = true;
                    }
                }

                if (frameContext->external)
                    returnFrame(frameContext, f);
            } else if (f) {
                for (size_t i = 0; i < frameContextRef->notifyCtxList.size(); i++) {
                    PVSFrameContext &notify = frameContextRef->notifyCtxList[i];
                    notify->availableFrames.push_back({frameContextRef->key, f});

                    assert(notify->numFrameRequests > 0);
                    if (--notify->numFrameRequests == 0) {
                        queueTask(notify);
                        needsSort = true;
                    }
                }

                if (frameContext->external)
                    returnFrame(frameContext, f);
            } else if (requestedFrames) {
                // already scheduled, do nothing
            } else {
                core->logFatal("No frame returned at the end of processing by " + node->name);
            }

            if (needsSort)
                tasks.sort(taskCmp);
            break;
        }

        if (!ranTask || activeThreads > maxThreads) {
            --activeThreads;
            if (stop) {
                lock.unlock();
                break;
            }
            if (++idleThreads == allThreads.size())
                allIdle.notify_one();

            newWork.wait(lock);
            --idleThreads;
            ++activeThreads;
        }
    }
}

VSThreadPool::VSThreadPool(VSCore *core) : core(core), activeThreads(0), idleThreads(0), reqCounter(0), stopThreads(false), ticks(0) {
    setThreadCount(0);
}

size_t VSThreadPool::threadCount() {
    std::lock_guard<std::mutex> l(taskLock);
    return maxThreads;
}

void VSThreadPool::spawnThread() {
    std::thread *thread = new std::thread(runTasksWrapper, this, std::ref(stopThreads));
    allThreads.insert(std::make_pair(thread->get_id(), thread));
    ++activeThreads;
}

size_t VSThreadPool::setThreadCount(size_t threads) {
    std::lock_guard<std::mutex> l(taskLock);
    maxThreads = threads > 0 ? threads : getNumAvailableThreads();
    if (maxThreads == 0) {
        maxThreads = 1;
        core->logMessage(mtWarning, "Couldn't detect optimal number of threads. Thread count set to 1.");
    }
    return maxThreads;
}

void VSThreadPool::queueTask(const PVSFrameContext &ctx) {
    tasks.push_front(ctx);
    wakeThread();
}

void VSThreadPool::wakeThread() {
    if (activeThreads < maxThreads) {
        if (idleThreads == 0) // newly spawned threads are active so no need to notify an additional thread
            spawnThread();
        else
            newWork.notify_one();
    }
}

void VSThreadPool::releaseThread() {
    --activeThreads;
}

void VSThreadPool::reserveThread() {
    ++activeThreads;
}

void VSThreadPool::startExternal(const PVSFrameContext &context) {
    assert(context);
    std::lock_guard<std::mutex> l(taskLock);
    context->reqOrder = ++reqCounter;
    tasks.push_back(context); // external requests can't be combined so just add to queue
    wakeThread();
}

void VSThreadPool::returnFrame(const VSFrameContext *rCtx, const PVSFrame &f) {
    assert(rCtx->frameDone);
    bool outputLock = rCtx->lockOnOutput;
    // we need to unlock here so the callback may request more frames without causing a deadlock
    // AND so that slow callbacks will only block operations in this thread, not all the others
    taskLock.unlock();
    if (rCtx->hasError()) {
        if (outputLock)
            callbackLock.lock();
        rCtx->frameDone(rCtx->userData, nullptr, rCtx->key.second, rCtx->key.first, rCtx->errorMessage.c_str());
        if (outputLock)
            callbackLock.unlock();
    } else {
        f->add_ref();
        if (outputLock)
            callbackLock.lock();
        rCtx->frameDone(rCtx->userData, f.get(), rCtx->key.second, rCtx->key.first, nullptr);
        if (outputLock)
            callbackLock.unlock();
    }
    taskLock.lock();
}

void VSThreadPool::startInternalRequest(const PVSFrameContext &notify, NodeOutputKey key) {
    //technically this could be done by walking up the context chain and add a new notification to the correct one
    //unfortunately this would probably be quite slow for deep scripts so just hope the cache catches it

    if (key.second < 0)
        core->logFatal("Negative frame request by: " + notify->key.first->getName());

    // check to see if it's time to reevaluate cache sizes
    if (core->memory->isOverLimit()) {
        ticks = 0;
        core->notifyCaches(true);
    } else if (++ticks == 500) { // a normal tick for caches to adjust their sizes based on recent history
        ticks = 0;
        core->notifyCaches(false);
    }

    auto it = allContexts.find(key);
    if (it != allContexts.end()) {
        PVSFrameContext &ctx = it->second;
        ctx->notifyCtxList.push_back(notify);
        ctx->reqOrder = std::min(ctx->reqOrder, notify->reqOrder);
    } else {
        PVSFrameContext ctx = new VSFrameContext(key, notify);
        // create a new context and append it to the tasks
        allContexts.insert(std::make_pair(key, ctx));
        queueTask(ctx);
    }
}

bool VSThreadPool::isWorkerThread() {
    std::lock_guard<std::mutex> m(taskLock);
    return allThreads.count(std::this_thread::get_id()) > 0;
}

void VSThreadPool::waitForDone() {
    std::unique_lock<std::mutex> m(taskLock);
    if (idleThreads < allThreads.size())
        allIdle.wait(m);
}

VSThreadPool::~VSThreadPool() {
    std::unique_lock<std::mutex> m(taskLock);
    stopThreads = true;

    while (!allThreads.empty()) {
        auto iter = allThreads.begin();
        auto thread = iter->second;
        newWork.notify_all();
        m.unlock();
        thread->join();
        m.lock();
        allThreads.erase(iter);
        delete thread;
        newWork.notify_all();
    }

    assert(activeThreads == 0);
    assert(idleThreads == 0);
};
<commit_msg>Fix threading bug<commit_after>/*
* Copyright (c) 2012-2021 Fredrik Mellbin
*
* This file is part of VapourSynth.
*
* VapourSynth is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* VapourSynth is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with VapourSynth; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "vscore.h"
#include <cassert>
#include <bitset>
#ifdef VS_TARGET_CPU_X86
#include "x86utils.h"
#endif

#if defined(HAVE_SCHED_GETAFFINITY)
#include <sched.h>
#elif defined(HAVE_CPUSET_GETAFFINITY)
#include <sys/param.h>
#include <sys/_cpuset.h>
#include <sys/cpuset.h>
#endif

size_t VSThreadPool::getNumAvailableThreads() {
    size_t nthreads = std::thread::hardware_concurrency();
#ifdef _WIN32
    DWORD_PTR pAff = 0;
    DWORD_PTR sAff = 0;
    BOOL res = GetProcessAffinityMask(GetCurrentProcess(), &pAff, &sAff);
    if (res && pAff != 0) {
        std::bitset<sizeof(sAff) * 8> b(pAff);
        nthreads = b.count();
    }
#elif defined(HAVE_SCHED_GETAFFINITY)
    // Linux only.
    cpu_set_t affinity;
    if (sched_getaffinity(0, sizeof(cpu_set_t), &affinity) == 0)
        nthreads = CPU_COUNT(&affinity);
#elif defined(HAVE_CPUSET_GETAFFINITY)
    // BSD only (FreeBSD only?)
    cpuset_t affinity;
    if (cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1, sizeof(cpuset_t), &affinity) == 0)
        nthreads = CPU_COUNT(&affinity);
#endif

    return nthreads;
}

bool VSThreadPool::taskCmp(const PVSFrameContext &a, const PVSFrameContext &b) {
    return (a->reqOrder < b->reqOrder) || (a->reqOrder == b->reqOrder && a->key.second < b->key.second);
}

void VSThreadPool::runTasksWrapper(VSThreadPool *owner, std::atomic<bool> &stop) {
    owner->runTasks(stop);
}

void VSThreadPool::runTasks(std::atomic<bool> &stop) {
#ifdef VS_TARGET_OS_WINDOWS
    if (!vs_isSSEStateOk())
        core->logFatal("Bad SSE state detected after creating new thread");
#endif

    std::unique_lock<std::mutex> lock(taskLock);

    while (true) {
        bool ranTask = false;

/////////////////////////////////////////////////////////////////////////////////////////////
// Go through all tasks from the top (oldest) and process the first one possible

        std::set<VSNode *> seenNodes;

        for (auto iter = tasks.begin(); iter != tasks.end(); ++iter) {
            VSFrameContext *frameContext = iter->get();
            VSNode *node = frameContext->key.first;

/////////////////////////////////////////////////////////////////////////////////////////////
// Fast path if a frame is cached

            if (node->cacheEnabled) {
                PVSFrame f = node->getCachedFrameInternal(frameContext->key.second);

                if (f) {
                    bool needsSort = false;

                    for (size_t i = 0; i < frameContext->notifyCtxList.size(); i++) {
                        PVSFrameContext &notify = frameContext->notifyCtxList[i];
                        notify->availableFrames.push_back({frameContext->key, f});

                        assert(notify->numFrameRequests > 0);
                        if (--notify->numFrameRequests == 0) {
                            queueTask(notify);
                            needsSort = true;
                        }
                    }

                    PVSFrameContext mainContextRef = std::move(*iter);
                    tasks.erase(iter);
                    allContexts.erase(frameContext->key);

                    if (frameContext->external)
                        returnFrame(frameContext, f);

                    if (needsSort)
                        tasks.sort(taskCmp);

                    ranTask = true;
                    break;
                }
            }

/////////////////////////////////////////////////////////////////////////////////////////////
// This part handles the locking for the different filter modes

            int filterMode = node->filterMode;

            // Don't try to lock the same node twice since it's likely to fail and will produce more out of order requests as well
            if (filterMode != fmFrameState && !seenNodes.insert(node).second)
                continue;

            // Does the filter need the per instance mutex? fmFrameState, fmUnordered and fmParallelRequests (when in the arAllFramesReady state) use this
            bool useSerialLock = (filterMode == fmFrameState || filterMode == fmUnordered || (filterMode == fmParallelRequests && !frameContext->first));

            if (useSerialLock) {
                if (!node->serialMutex.try_lock())
                    continue;
                if (filterMode == fmFrameState) {
                    if (node->serialFrame == -1) {
                        node->serialFrame = frameContext->key.second;
                        // another frame already in progress?
                    } else if (node->serialFrame != frameContext->key.second) {
                        node->serialMutex.unlock();
                        continue;
                    }
                }
            }

/////////////////////////////////////////////////////////////////////////////////////////////
// Remove the context from the task list and keep references around until processing is done

            PVSFrameContext frameContextRef = std::move(*iter);
            tasks.erase(iter);

/////////////////////////////////////////////////////////////////////////////////////////////
// Figure out the activation reason

            assert(frameContext->numFrameRequests == 0);
            int ar = arInitial;
            if (frameContext->hasError()) {
                ar = arError;
            } else if (!frameContext->first) {
                ar = (node->apiMajor == 3) ? static_cast<int>(vs3::arAllFramesReady) : static_cast<int>(arAllFramesReady);
            } else if (frameContext->first) {
                frameContext->first = false;
            }

/////////////////////////////////////////////////////////////////////////////////////////////
// Do the actual processing

            lock.unlock();

            PVSFrame f = node->getFrameInternal(frameContext->key.second, ar, frameContext);
            ranTask = true;

            bool frameProcessingDone = f || frameContext->hasError();
            if (frameContext->hasError() && f)
                core->logFatal("A frame was returned by " + node->name + " but an error was also set, this is not allowed");

/////////////////////////////////////////////////////////////////////////////////////////////
// Unlock so the next job can run on the context
            if (useSerialLock) {
                if (frameProcessingDone && filterMode == fmFrameState)
                    node->serialFrame = -1;
                node->serialMutex.unlock();
            }

/////////////////////////////////////////////////////////////////////////////////////////////
// Handle frames that were requested
            bool requestedFrames = frameContext->reqList.size() > 0 && !frameProcessingDone;
            bool needsSort = false;
            if (f && requestedFrames)
                core->logFatal("A frame was returned at the end of processing by " + node->name + " but there are still outstanding requests");

            lock.lock();

            if (requestedFrames) {
                assert(frameContext->numFrameRequests == 0);

                for (size_t i = 0; i < frameContext->reqList.size(); i++)
                    startInternalRequest(frameContextRef, frameContext->reqList[i]);

                frameContext->numFrameRequests = frameContext->reqList.size();
                frameContext->reqList.clear();
            }

            if (frameProcessingDone)
                allContexts.erase(frameContext->key);

/////////////////////////////////////////////////////////////////////////////////////////////
// Notify all dependent contexts

            if (frameContext->hasError()) {
                for (size_t i = 0; i < frameContextRef->notifyCtxList.size(); i++) {
                    PVSFrameContext &notify = frameContextRef->notifyCtxList[i];
                    notify->setError(frameContextRef->getErrorMessage());

                    assert(notify->numFrameRequests > 0);
                    if (--notify->numFrameRequests == 0) {
                        queueTask(notify);
                        needsSort = true;
                    }
                }

                if (frameContext->external)
                    returnFrame(frameContext, f);
            } else if (f) {
                for (size_t i = 0; i < frameContextRef->notifyCtxList.size(); i++) {
                    PVSFrameContext &notify = frameContextRef->notifyCtxList[i];
                    notify->availableFrames.push_back({frameContextRef->key, f});

                    assert(notify->numFrameRequests > 0);
                    if (--notify->numFrameRequests == 0) {
                        queueTask(notify);
                        needsSort = true;
                    }
                }

                if (frameContext->external)
                    returnFrame(frameContext, f);
            } else if (requestedFrames) {
                // already scheduled, do nothing
            } else {
                core->logFatal("No frame returned at the end of processing by " + node->name);
            }

            if (needsSort)
                tasks.sort(taskCmp);
            break;
        }

        if (!ranTask || activeThreads > maxThreads) {
            --activeThreads;
            if (stop) {
                lock.unlock();
                break;
            }
            if (++idleThreads == allThreads.size())
                allIdle.notify_one();

            newWork.wait(lock);
            --idleThreads;
            ++activeThreads;
        }
    }
}

VSThreadPool::VSThreadPool(VSCore *core) : core(core), activeThreads(0), idleThreads(0), reqCounter(0), stopThreads(false), ticks(0) {
    setThreadCount(0);
}

size_t VSThreadPool::threadCount() {
    std::lock_guard<std::mutex> l(taskLock);
    return maxThreads;
}

void VSThreadPool::spawnThread() {
    std::thread *thread = new std::thread(runTasksWrapper, this, std::ref(stopThreads));
    allThreads.insert(std::make_pair(thread->get_id(), thread));
    ++activeThreads;
}

size_t VSThreadPool::setThreadCount(size_t threads) {
    std::lock_guard<std::mutex> l(taskLock);
    maxThreads = threads > 0 ? threads : getNumAvailableThreads();
    if (maxThreads == 0) {
        maxThreads = 1;
        core->logMessage(mtWarning, "Couldn't detect optimal number of threads. Thread count set to 1.");
    }
    return maxThreads;
}

void VSThreadPool::queueTask(const PVSFrameContext &ctx) {
    assert(ctx);
    tasks.push_front(ctx);
    wakeThread();
}

void VSThreadPool::wakeThread() {
    if (activeThreads < maxThreads) {
        if (idleThreads == 0) // newly spawned threads are active so no need to notify an additional thread
            spawnThread();
        else
            newWork.notify_one();
    }
}

void VSThreadPool::releaseThread() {
    --activeThreads;
}

void VSThreadPool::reserveThread() {
    ++activeThreads;
}

void VSThreadPool::startExternal(const PVSFrameContext &context) {
    assert(context);
    std::lock_guard<std::mutex> l(taskLock);
    context->reqOrder = ++reqCounter;
    assert(context);
    tasks.push_back(context); // external requests can't be combined so just add to queue
    wakeThread();
}

void VSThreadPool::returnFrame(const VSFrameContext *rCtx, const PVSFrame &f) {
    assert(rCtx->frameDone);
    bool outputLock = rCtx->lockOnOutput;
    // we need to unlock here so the callback may request more frames without causing a deadlock
    // AND so that slow callbacks will only block operations in this thread, not all the others
    taskLock.unlock();
    if (rCtx->hasError()) {
        if (outputLock)
            callbackLock.lock();
        rCtx->frameDone(rCtx->userData, nullptr, rCtx->key.second, rCtx->key.first, rCtx->errorMessage.c_str());
        if (outputLock)
            callbackLock.unlock();
    } else {
        f->add_ref();
        if (outputLock)
            callbackLock.lock();
        rCtx->frameDone(rCtx->userData, f.get(), rCtx->key.second, rCtx->key.first, nullptr);
        if (outputLock)
            callbackLock.unlock();
    }
    taskLock.lock();
}

void VSThreadPool::startInternalRequest(const PVSFrameContext &notify, NodeOutputKey key) {
    //technically this could be done by walking up the context chain and add a new notification to the correct one
    //unfortunately this would probably be quite slow for deep scripts so just hope the cache catches it

    if (key.second < 0)
        core->logFatal("Negative frame request by: " + notify->key.first->getName());

    // check to see if it's time to reevaluate cache sizes
    if (core->memory->isOverLimit()) {
        ticks = 0;
        core->notifyCaches(true);
    } else if (++ticks == 500) { // a normal tick for caches to adjust their sizes based on recent history
        ticks = 0;
        core->notifyCaches(false);
    }

    auto it = allContexts.find(key);
    if (it != allContexts.end()) {
        PVSFrameContext &ctx = it->second;
        ctx->notifyCtxList.push_back(notify);
        ctx->reqOrder = std::min(ctx->reqOrder, notify->reqOrder);
    } else {
        PVSFrameContext ctx = new VSFrameContext(key, notify);
        // create a new context and append it to the tasks
        allContexts.insert(std::make_pair(key, ctx));
        queueTask(ctx);
    }
}

bool VSThreadPool::isWorkerThread() {
    std::lock_guard<std::mutex> m(taskLock);
    return allThreads.count(std::this_thread::get_id()) > 0;
}

void VSThreadPool::waitForDone() {
    std::unique_lock<std::mutex> m(taskLock);
    if (idleThreads < allThreads.size())
        allIdle.wait(m);
}

VSThreadPool::~VSThreadPool() {
    std::unique_lock<std::mutex> m(taskLock);
    stopThreads = true;

    while (!allThreads.empty()) {
        auto iter = allThreads.begin();
        auto thread = iter->second;
        newWork.notify_all();
        m.unlock();
        thread->join();
        m.lock();
        allThreads.erase(iter);
        delete thread;
        newWork.notify_all();
    }

    assert(activeThreads == 0);
    assert(idleThreads == 0);
};
<|endoftext|>


<commit_before>/*
* Copyright (c) 2012-2013 Fredrik Mellbin
*
* This file is part of VapourSynth.
*
* VapourSynth is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* VapourSynth is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with VapourSynth; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "vscore.h"
#include <assert.h>
#ifdef VS_TARGET_CPU_X86
#include "x86utils.h"
#endif

void VSThreadPool::runTasks(VSThreadPool *owner, std::atomic<bool> &stop) {
#ifdef VS_TARGET_CPU_X86
    if (!vs_isMMXStateOk())
        vsFatal("Bad MMX state detected after creating new thread");
#endif
#ifdef VS_TARGET_OS_WINDOWS
    if (!vs_isFPUStateOk())
        vsWarning("Bad FPU state detected after creating new thread");
    if (!vs_isSSEStateOk())
        vsFatal("Bad SSE state detected after creating new thread");
#endif

    std::unique_lock<std::mutex> lock(owner->lock);

    while (true) {
        bool ranTask = false;

/////////////////////////////////////////////////////////////////////////////////////////////
// Go through all tasks from the top (oldest) and process the first one possible
        for (std::list<PFrameContext>::iterator iter = owner->tasks.begin(); iter != owner->tasks.end(); ++iter) {
            FrameContext *mainContext = iter->get();
            FrameContext *leafContext = NULL;

/////////////////////////////////////////////////////////////////////////////////////////////
// Handle the output tasks
            if (mainContext->frameDone && mainContext->returnedFrame) {
                PFrameContext mainContextRef(*iter);
                owner->tasks.erase(iter);
                owner->returnFrame(mainContextRef, mainContext->returnedFrame);
                ranTask = true;
                break;
            }

            if (mainContext->frameDone && mainContext->hasError()) {
                PFrameContext mainContextRef(*iter);
                owner->tasks.erase(iter);
                owner->returnFrame(mainContextRef, mainContext->getErrorMessage());
                ranTask = true;
                break;
            }

            bool hasLeafContext = mainContext->returnedFrame || mainContext->hasError();
            if (hasLeafContext)
            {
                leafContext = mainContext;
                mainContext = mainContext->upstreamContext.get();
            }

            VSNode *clip = mainContext->clip;
            int filterMode = clip->filterMode;

/////////////////////////////////////////////////////////////////////////////////////////////
// This part handles the locking for the different filter modes

            bool parallelRequestsNeedsUnlock = false;
            if (filterMode == fmUnordered) {
                // already busy?
                if (!clip->serialMutex.try_lock())
                    continue;
            } else if (filterMode == fmSerial) {
                // already busy?
                if (!clip->serialMutex.try_lock())
                    continue;
                // no frame in progress?
                if (clip->serialFrame == -1) {
                    clip->serialFrame = mainContext->n;
                //
                } else if (clip->serialFrame != mainContext->n) {
                    clip->serialMutex.unlock();
                    continue;
                }
                // continue processing the already started frame
            } else if (filterMode == fmParallel) {
                std::lock_guard<std::mutex> lock(clip->concurrentFramesMutex);
                // is the filter already processing another call for this frame? if so move along
                if (clip->concurrentFrames.count(mainContext->n)) {
                    continue;
                } else {
                    clip->concurrentFrames.insert(mainContext->n);
                }
            } else if (filterMode == fmParallelRequests) {
                std::lock_guard<std::mutex> lock(clip->concurrentFramesMutex);
                // is the filter already processing another call for this frame? if so move along
                if (clip->concurrentFrames.count(mainContext->n)) {
                    continue;
                } else {
                    // do we need the serial lock since all frames will be ready this time?
                    // check if we're in the arAllFramesReady state so we need additional locking
                    if (mainContext->numFrameRequests == 1) {
                        if (!clip->serialMutex.try_lock())
                            continue;
                        parallelRequestsNeedsUnlock = true;
                        clip->concurrentFrames.insert(mainContext->n);
                    }
                }
            }

/////////////////////////////////////////////////////////////////////////////////////////////
// Remove the context from the task list

            PFrameContext mainContextRef;
            PFrameContext leafContextRef;
            if (hasLeafContext) {
                leafContextRef = *iter;
                mainContextRef = leafContextRef->upstreamContext;
            } else {
                mainContextRef = *iter;
            }

            owner->tasks.erase(iter);

            /////////////////////////////////////////////////////////////////////////////////////////////
            // Figure out the activation reason

            VSActivationReason ar = arInitial;
            bool skipCall = false; // Used to avoid multiple error calls for the same frame request going into a filter
            if (hasLeafContext && leafContext->hasError() || mainContext->hasError()) {
                ar = arError;
                skipCall = mainContext->setError(leafContext->getErrorMessage());
                --mainContext->numFrameRequests;
            } else if (hasLeafContext && leafContext->returnedFrame) {
                if (--mainContext->numFrameRequests > 0)
                    ar = arFrameReady;
                else
                    ar = arAllFramesReady;

                mainContext->availableFrames.insert(std::make_pair(NodeOutputKey(leafContext->clip, leafContext->n, leafContext->index), leafContext->returnedFrame));
                mainContext->lastCompletedN = leafContext->n;
                mainContext->lastCompletedNode = leafContext->node;
            }

            assert(mainContext->numFrameRequests >= 0);

            bool hasExistingRequests = !!mainContext->numFrameRequests;

/////////////////////////////////////////////////////////////////////////////////////////////
// Do the actual processing

            lock.unlock();

            VSFrameContext externalFrameCtx(mainContextRef);
            assert(ar == arError || !mainContext->hasError());
            PVideoFrame f;
            if (!skipCall)
                f = clip->getFrameInternal(mainContext->n, ar, externalFrameCtx);
            ranTask = true;
            bool frameProcessingDone = f || mainContext->hasError();

/////////////////////////////////////////////////////////////////////////////////////////////
// Unlock so the next job can run on the context
            if (filterMode == fmUnordered) {
                clip->serialMutex.unlock();
            } else if (filterMode == fmSerial) {
                if (frameProcessingDone)
                    clip->serialFrame = -1;
                clip->serialMutex.unlock();
            } else if (filterMode == fmParallel) {
                std::lock_guard<std::mutex> lock(clip->concurrentFramesMutex);
                clip->concurrentFrames.erase(mainContext->n);
            } else if (filterMode == fmParallelRequests) {
                std::lock_guard<std::mutex> lock(clip->concurrentFramesMutex);
                clip->concurrentFrames.erase(mainContext->n);
                if (parallelRequestsNeedsUnlock)
                    clip->serialMutex.unlock();
            }

/////////////////////////////////////////////////////////////////////////////////////////////
// Handle frames that were requested
            bool requestedFrames = !externalFrameCtx.reqList.empty() && !frameProcessingDone;

            lock.lock();

            if (requestedFrames) {
                for (auto &reqIter : externalFrameCtx.reqList)
                    owner->startInternal(reqIter);
                externalFrameCtx.reqList.clear();
            }

            if (frameProcessingDone)
                owner->allContexts.erase(NodeOutputKey(mainContext->clip, mainContext->n, mainContext->index));

/////////////////////////////////////////////////////////////////////////////////////////////
// Propagate status to other linked contexts
// CHANGES mainContextRef!!!

            if (mainContext->hasError() && !hasExistingRequests && !requestedFrames) {
                PFrameContext n;
                do {
                    n = mainContextRef->notificationChain;

                    if (n) {
                        mainContextRef->notificationChain.reset();
                        n->setError(mainContextRef->getErrorMessage());
                    }

                    if (mainContextRef->upstreamContext) {
                        owner->startInternal(mainContextRef);
                    }

                    if (mainContextRef->frameDone) {
                        owner->returnFrame(mainContextRef, mainContextRef->getErrorMessage());
                    }
                } while ((mainContextRef = n));
            } else if (f) {
                if (hasExistingRequests || requestedFrames)
                    vsFatal("A frame was returned at the end of processing by %s but there are still outstanding requests", clip->name.c_str());
                PFrameContext n;

                do {
                    n = mainContextRef->notificationChain;

                    if (n)
                        mainContextRef->notificationChain.reset();

                    if (mainContextRef->upstreamContext) {
                        mainContextRef->returnedFrame = f;
                        owner->startInternal(mainContextRef);
                    }

                    if (mainContextRef->frameDone)
                        owner->returnFrame(mainContextRef, f);
                } while ((mainContextRef = n));
            } else if (hasExistingRequests || requestedFrames) {
                // already scheduled, do nothing
            } else {
                vsFatal("No frame returned at the end of processing by %s", clip->name.c_str());
            }
            break;
        }

        if (!ranTask || owner->activeThreadCount() > owner->threadCount()) {
            --owner->activeThreads;
            if (stop) {
                lock.unlock();
                break;
            }
            ++owner->idleThreads;
            owner->newWork.wait(lock);
            --owner->idleThreads;
            ++owner->activeThreads;
        }
    }
}

VSThreadPool::VSThreadPool(VSCore *core, int threads) : core(core), activeThreads(0), idleThreads(0), stopThreads(false), ticks(0) {
    setThreadCount(threads);
}

int VSThreadPool::activeThreadCount() const {
    return activeThreads;
}

int VSThreadPool::threadCount() const {
    return maxThreads;
}

void VSThreadPool::spawnThread() {
    std::thread *thread = new std::thread(runTasks, this, std::ref(stopThreads));
    allThreads.insert(std::make_pair(thread->get_id(), thread));
    ++activeThreads;
}

void VSThreadPool::setThreadCount(int threads) {
    maxThreads = threads > 0 ? threads : std::thread::hardware_concurrency();
    if (maxThreads == 0) {
        maxThreads = 1;
        vsWarning("Couldn't detect optimal number of threads. Thread count set to 1.");
    }
}

void VSThreadPool::wakeThread() {
    if (activeThreads < maxThreads) {
        if (idleThreads == 0) // newly spawned threads are active so no need to notify an additional thread
            spawnThread();
        else
            newWork.notify_one();
    }
}

void VSThreadPool::releaseThread() {
    --activeThreads;
}

void VSThreadPool::reserveThread() {
    ++activeThreads;
}

void VSThreadPool::notifyCaches(bool needMemory) {
    std::lock_guard<std::mutex> lock(core->cacheLock);
    for (auto &cache : core->caches)
        cache->notifyCache(needMemory);
}

void VSThreadPool::start(const PFrameContext &context) {
    assert(context);
    std::lock_guard<std::mutex> l(lock);
    startInternal(context);
}

void VSThreadPool::returnFrame(const PFrameContext &rCtx, const PVideoFrame &f) {
    assert(rCtx->frameDone);
    // we need to unlock here so the callback may request more frames without causing a deadlock
    // AND so that slow callbacks will only block operations in this thread, not all the others
    lock.unlock();
    VSFrameRef *ref = new VSFrameRef(f);
    callbackLock.lock();
    rCtx->frameDone(rCtx->userData, ref, rCtx->n, rCtx->node, NULL);
    callbackLock.unlock();
    lock.lock();
}

void VSThreadPool::returnFrame(const PFrameContext &rCtx, const std::string &errMsg) {
    assert(rCtx->frameDone);
    // we need to unlock here so the callback may request more frames without causing a deadlock
    // AND so that slow callbacks will only block operations in this thread, not all the others
    lock.unlock();
    callbackLock.lock();
    rCtx->frameDone(rCtx->userData, NULL, rCtx->n, rCtx->node, errMsg.c_str());
    callbackLock.unlock();
    lock.lock();
}

void VSThreadPool::startInternal(const PFrameContext &context) {
    //technically this could be done by walking up the context chain and add a new notification to the correct one
    //unfortunately this would probably be quite slow for deep scripts so just hope the cache catches it

    if (context->n < 0)
        vsFatal("Negative frame request by: %s", context->clip->getName().c_str());

    // check to see if it's time to reevaluate cache sizes
    if (core->memory->isOverLimit()) {
        ticks = 0;
        notifyCaches(true);
    }

    // a normal tick for caches to adjust their sizes based on recent history
    if (!context->upstreamContext && ++ticks == 500) {
        ticks = 0;
        notifyCaches(false);
    }

    // add it immediately if the task is to return a completed frame or report an error since it never has an existing context
    if (context->returnedFrame || context->hasError()) {
        tasks.push_back(context);
    } else {
        if (context->upstreamContext)
            ++context->upstreamContext->numFrameRequests;

        NodeOutputKey p(context->clip, context->n, context->index);

        if (allContexts.count(p)) {
            PFrameContext &ctx = allContexts[p];
            assert(context->clip == ctx->clip && context->n == ctx->n && context->index == ctx->index);

            if (ctx->returnedFrame) {
                // special case where the requested frame is encountered "by accident"
                context->returnedFrame = ctx->returnedFrame;
                tasks.push_back(context);
            } else {
                // add it to the list of contexts to notify when it's available
                context->notificationChain = ctx->notificationChain;
                ctx->notificationChain = context;
            }
        } else {
            // create a new context and append it to the tasks
            allContexts[p] = context;
            tasks.push_back(context);
        }
    }
    wakeThread();
}

bool VSThreadPool::isWorkerThread() {
    std::lock_guard<std::mutex> m(lock);
    return allThreads.count(std::this_thread::get_id()) > 0;
}

void VSThreadPool::waitForDone() {
    // todo
}

VSThreadPool::~VSThreadPool() {
    std::unique_lock<std::mutex> m(lock);
    stopThreads = true;

    while (!allThreads.empty()) {
        auto iter = allThreads.begin();
        auto thread = iter->second;
        newWork.notify_all();
        m.unlock();
        thread->join();
        m.lock();
        allThreads.erase(iter);
        delete thread;
        newWork.notify_all();
    }

    assert(activeThreads == 0);
    assert(idleThreads == 0);
};
<commit_msg>add parentheses around '&&'<commit_after>/*
* Copyright (c) 2012-2013 Fredrik Mellbin
*
* This file is part of VapourSynth.
*
* VapourSynth is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* VapourSynth is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with VapourSynth; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "vscore.h"
#include <assert.h>
#ifdef VS_TARGET_CPU_X86
#include "x86utils.h"
#endif

void VSThreadPool::runTasks(VSThreadPool *owner, std::atomic<bool> &stop) {
#ifdef VS_TARGET_CPU_X86
    if (!vs_isMMXStateOk())
        vsFatal("Bad MMX state detected after creating new thread");
#endif
#ifdef VS_TARGET_OS_WINDOWS
    if (!vs_isFPUStateOk())
        vsWarning("Bad FPU state detected after creating new thread");
    if (!vs_isSSEStateOk())
        vsFatal("Bad SSE state detected after creating new thread");
#endif

    std::unique_lock<std::mutex> lock(owner->lock);

    while (true) {
        bool ranTask = false;

/////////////////////////////////////////////////////////////////////////////////////////////
// Go through all tasks from the top (oldest) and process the first one possible
        for (std::list<PFrameContext>::iterator iter = owner->tasks.begin(); iter != owner->tasks.end(); ++iter) {
            FrameContext *mainContext = iter->get();
            FrameContext *leafContext = NULL;

/////////////////////////////////////////////////////////////////////////////////////////////
// Handle the output tasks
            if (mainContext->frameDone && mainContext->returnedFrame) {
                PFrameContext mainContextRef(*iter);
                owner->tasks.erase(iter);
                owner->returnFrame(mainContextRef, mainContext->returnedFrame);
                ranTask = true;
                break;
            }

            if (mainContext->frameDone && mainContext->hasError()) {
                PFrameContext mainContextRef(*iter);
                owner->tasks.erase(iter);
                owner->returnFrame(mainContextRef, mainContext->getErrorMessage());
                ranTask = true;
                break;
            }

            bool hasLeafContext = mainContext->returnedFrame || mainContext->hasError();
            if (hasLeafContext)
            {
                leafContext = mainContext;
                mainContext = mainContext->upstreamContext.get();
            }

            VSNode *clip = mainContext->clip;
            int filterMode = clip->filterMode;

/////////////////////////////////////////////////////////////////////////////////////////////
// This part handles the locking for the different filter modes

            bool parallelRequestsNeedsUnlock = false;
            if (filterMode == fmUnordered) {
                // already busy?
                if (!clip->serialMutex.try_lock())
                    continue;
            } else if (filterMode == fmSerial) {
                // already busy?
                if (!clip->serialMutex.try_lock())
                    continue;
                // no frame in progress?
                if (clip->serialFrame == -1) {
                    clip->serialFrame = mainContext->n;
                //
                } else if (clip->serialFrame != mainContext->n) {
                    clip->serialMutex.unlock();
                    continue;
                }
                // continue processing the already started frame
            } else if (filterMode == fmParallel) {
                std::lock_guard<std::mutex> lock(clip->concurrentFramesMutex);
                // is the filter already processing another call for this frame? if so move along
                if (clip->concurrentFrames.count(mainContext->n)) {
                    continue;
                } else {
                    clip->concurrentFrames.insert(mainContext->n);
                }
            } else if (filterMode == fmParallelRequests) {
                std::lock_guard<std::mutex> lock(clip->concurrentFramesMutex);
                // is the filter already processing another call for this frame? if so move along
                if (clip->concurrentFrames.count(mainContext->n)) {
                    continue;
                } else {
                    // do we need the serial lock since all frames will be ready this time?
                    // check if we're in the arAllFramesReady state so we need additional locking
                    if (mainContext->numFrameRequests == 1) {
                        if (!clip->serialMutex.try_lock())
                            continue;
                        parallelRequestsNeedsUnlock = true;
                        clip->concurrentFrames.insert(mainContext->n);
                    }
                }
            }

/////////////////////////////////////////////////////////////////////////////////////////////
// Remove the context from the task list

            PFrameContext mainContextRef;
            PFrameContext leafContextRef;
            if (hasLeafContext) {
                leafContextRef = *iter;
                mainContextRef = leafContextRef->upstreamContext;
            } else {
                mainContextRef = *iter;
            }

            owner->tasks.erase(iter);

            /////////////////////////////////////////////////////////////////////////////////////////////
            // Figure out the activation reason

            VSActivationReason ar = arInitial;
            bool skipCall = false; // Used to avoid multiple error calls for the same frame request going into a filter
            if ((hasLeafContext && leafContext->hasError()) || mainContext->hasError()) {
                ar = arError;
                skipCall = mainContext->setError(leafContext->getErrorMessage());
                --mainContext->numFrameRequests;
            } else if (hasLeafContext && leafContext->returnedFrame) {
                if (--mainContext->numFrameRequests > 0)
                    ar = arFrameReady;
                else
                    ar = arAllFramesReady;

                mainContext->availableFrames.insert(std::make_pair(NodeOutputKey(leafContext->clip, leafContext->n, leafContext->index), leafContext->returnedFrame));
                mainContext->lastCompletedN = leafContext->n;
                mainContext->lastCompletedNode = leafContext->node;
            }

            assert(mainContext->numFrameRequests >= 0);

            bool hasExistingRequests = !!mainContext->numFrameRequests;

/////////////////////////////////////////////////////////////////////////////////////////////
// Do the actual processing

            lock.unlock();

            VSFrameContext externalFrameCtx(mainContextRef);
            assert(ar == arError || !mainContext->hasError());
            PVideoFrame f;
            if (!skipCall)
                f = clip->getFrameInternal(mainContext->n, ar, externalFrameCtx);
            ranTask = true;
            bool frameProcessingDone = f || mainContext->hasError();

/////////////////////////////////////////////////////////////////////////////////////////////
// Unlock so the next job can run on the context
            if (filterMode == fmUnordered) {
                clip->serialMutex.unlock();
            } else if (filterMode == fmSerial) {
                if (frameProcessingDone)
                    clip->serialFrame = -1;
                clip->serialMutex.unlock();
            } else if (filterMode == fmParallel) {
                std::lock_guard<std::mutex> lock(clip->concurrentFramesMutex);
                clip->concurrentFrames.erase(mainContext->n);
            } else if (filterMode == fmParallelRequests) {
                std::lock_guard<std::mutex> lock(clip->concurrentFramesMutex);
                clip->concurrentFrames.erase(mainContext->n);
                if (parallelRequestsNeedsUnlock)
                    clip->serialMutex.unlock();
            }

/////////////////////////////////////////////////////////////////////////////////////////////
// Handle frames that were requested
            bool requestedFrames = !externalFrameCtx.reqList.empty() && !frameProcessingDone;

            lock.lock();

            if (requestedFrames) {
                for (auto &reqIter : externalFrameCtx.reqList)
                    owner->startInternal(reqIter);
                externalFrameCtx.reqList.clear();
            }

            if (frameProcessingDone)
                owner->allContexts.erase(NodeOutputKey(mainContext->clip, mainContext->n, mainContext->index));

/////////////////////////////////////////////////////////////////////////////////////////////
// Propagate status to other linked contexts
// CHANGES mainContextRef!!!

            if (mainContext->hasError() && !hasExistingRequests && !requestedFrames) {
                PFrameContext n;
                do {
                    n = mainContextRef->notificationChain;

                    if (n) {
                        mainContextRef->notificationChain.reset();
                        n->setError(mainContextRef->getErrorMessage());
                    }

                    if (mainContextRef->upstreamContext) {
                        owner->startInternal(mainContextRef);
                    }

                    if (mainContextRef->frameDone) {
                        owner->returnFrame(mainContextRef, mainContextRef->getErrorMessage());
                    }
                } while ((mainContextRef = n));
            } else if (f) {
                if (hasExistingRequests || requestedFrames)
                    vsFatal("A frame was returned at the end of processing by %s but there are still outstanding requests", clip->name.c_str());
                PFrameContext n;

                do {
                    n = mainContextRef->notificationChain;

                    if (n)
                        mainContextRef->notificationChain.reset();

                    if (mainContextRef->upstreamContext) {
                        mainContextRef->returnedFrame = f;
                        owner->startInternal(mainContextRef);
                    }

                    if (mainContextRef->frameDone)
                        owner->returnFrame(mainContextRef, f);
                } while ((mainContextRef = n));
            } else if (hasExistingRequests || requestedFrames) {
                // already scheduled, do nothing
            } else {
                vsFatal("No frame returned at the end of processing by %s", clip->name.c_str());
            }
            break;
        }

        if (!ranTask || owner->activeThreadCount() > owner->threadCount()) {
            --owner->activeThreads;
            if (stop) {
                lock.unlock();
                break;
            }
            ++owner->idleThreads;
            owner->newWork.wait(lock);
            --owner->idleThreads;
            ++owner->activeThreads;
        }
    }
}

VSThreadPool::VSThreadPool(VSCore *core, int threads) : core(core), activeThreads(0), idleThreads(0), stopThreads(false), ticks(0) {
    setThreadCount(threads);
}

int VSThreadPool::activeThreadCount() const {
    return activeThreads;
}

int VSThreadPool::threadCount() const {
    return maxThreads;
}

void VSThreadPool::spawnThread() {
    std::thread *thread = new std::thread(runTasks, this, std::ref(stopThreads));
    allThreads.insert(std::make_pair(thread->get_id(), thread));
    ++activeThreads;
}

void VSThreadPool::setThreadCount(int threads) {
    maxThreads = threads > 0 ? threads : std::thread::hardware_concurrency();
    if (maxThreads == 0) {
        maxThreads = 1;
        vsWarning("Couldn't detect optimal number of threads. Thread count set to 1.");
    }
}

void VSThreadPool::wakeThread() {
    if (activeThreads < maxThreads) {
        if (idleThreads == 0) // newly spawned threads are active so no need to notify an additional thread
            spawnThread();
        else
            newWork.notify_one();
    }
}

void VSThreadPool::releaseThread() {
    --activeThreads;
}

void VSThreadPool::reserveThread() {
    ++activeThreads;
}

void VSThreadPool::notifyCaches(bool needMemory) {
    std::lock_guard<std::mutex> lock(core->cacheLock);
    for (auto &cache : core->caches)
        cache->notifyCache(needMemory);
}

void VSThreadPool::start(const PFrameContext &context) {
    assert(context);
    std::lock_guard<std::mutex> l(lock);
    startInternal(context);
}

void VSThreadPool::returnFrame(const PFrameContext &rCtx, const PVideoFrame &f) {
    assert(rCtx->frameDone);
    // we need to unlock here so the callback may request more frames without causing a deadlock
    // AND so that slow callbacks will only block operations in this thread, not all the others
    lock.unlock();
    VSFrameRef *ref = new VSFrameRef(f);
    callbackLock.lock();
    rCtx->frameDone(rCtx->userData, ref, rCtx->n, rCtx->node, NULL);
    callbackLock.unlock();
    lock.lock();
}

void VSThreadPool::returnFrame(const PFrameContext &rCtx, const std::string &errMsg) {
    assert(rCtx->frameDone);
    // we need to unlock here so the callback may request more frames without causing a deadlock
    // AND so that slow callbacks will only block operations in this thread, not all the others
    lock.unlock();
    callbackLock.lock();
    rCtx->frameDone(rCtx->userData, NULL, rCtx->n, rCtx->node, errMsg.c_str());
    callbackLock.unlock();
    lock.lock();
}

void VSThreadPool::startInternal(const PFrameContext &context) {
    //technically this could be done by walking up the context chain and add a new notification to the correct one
    //unfortunately this would probably be quite slow for deep scripts so just hope the cache catches it

    if (context->n < 0)
        vsFatal("Negative frame request by: %s", context->clip->getName().c_str());

    // check to see if it's time to reevaluate cache sizes
    if (core->memory->isOverLimit()) {
        ticks = 0;
        notifyCaches(true);
    }

    // a normal tick for caches to adjust their sizes based on recent history
    if (!context->upstreamContext && ++ticks == 500) {
        ticks = 0;
        notifyCaches(false);
    }

    // add it immediately if the task is to return a completed frame or report an error since it never has an existing context
    if (context->returnedFrame || context->hasError()) {
        tasks.push_back(context);
    } else {
        if (context->upstreamContext)
            ++context->upstreamContext->numFrameRequests;

        NodeOutputKey p(context->clip, context->n, context->index);

        if (allContexts.count(p)) {
            PFrameContext &ctx = allContexts[p];
            assert(context->clip == ctx->clip && context->n == ctx->n && context->index == ctx->index);

            if (ctx->returnedFrame) {
                // special case where the requested frame is encountered "by accident"
                context->returnedFrame = ctx->returnedFrame;
                tasks.push_back(context);
            } else {
                // add it to the list of contexts to notify when it's available
                context->notificationChain = ctx->notificationChain;
                ctx->notificationChain = context;
            }
        } else {
            // create a new context and append it to the tasks
            allContexts[p] = context;
            tasks.push_back(context);
        }
    }
    wakeThread();
}

bool VSThreadPool::isWorkerThread() {
    std::lock_guard<std::mutex> m(lock);
    return allThreads.count(std::this_thread::get_id()) > 0;
}

void VSThreadPool::waitForDone() {
    // todo
}

VSThreadPool::~VSThreadPool() {
    std::unique_lock<std::mutex> m(lock);
    stopThreads = true;

    while (!allThreads.empty()) {
        auto iter = allThreads.begin();
        auto thread = iter->second;
        newWork.notify_all();
        m.unlock();
        thread->join();
        m.lock();
        allThreads.erase(iter);
        delete thread;
        newWork.notify_all();
    }

    assert(activeThreads == 0);
    assert(idleThreads == 0);
};
<|endoftext|>


<commit_before>// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#include "atom/common/native_mate_converters/net_converter.h"

#include <string>
#include <vector>

#include "atom/common/native_mate_converters/gurl_converter.h"
#include "atom/common/native_mate_converters/value_converter.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "native_mate/dictionary.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/upload_data_stream.h"
#include "net/base/upload_element_reader.h"
#include "net/base/upload_file_element_reader.h"
#include "net/cert/x509_certificate.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "storage/browser/blob/upload_blob_element_reader.h"

#include "atom/common/node_includes.h"

namespace mate {

namespace {

bool CertFromData(const std::string& data,
    scoped_refptr<net::X509Certificate>* out) {
  auto cert_list = net::X509Certificate::CreateCertificateListFromBytes(
    data.c_str(), data.length(),
    net::X509Certificate::FORMAT_SINGLE_CERTIFICATE);
  if (cert_list.empty())
    return false;

  auto leaf_cert = cert_list.front();
  if (!leaf_cert)
    return false;

  *out = leaf_cert;

  return true;
}

}

// static
v8::Local<v8::Value> Converter<const net::AuthChallengeInfo*>::ToV8(
    v8::Isolate* isolate, const net::AuthChallengeInfo* val) {
  mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate);
  dict.Set("isProxy", val->is_proxy);
  dict.Set("scheme", val->scheme);
  dict.Set("host", val->challenger.host());
  dict.Set("port", static_cast<uint32_t>(val->challenger.port()));
  dict.Set("realm", val->realm);
  return mate::ConvertToV8(isolate, dict);
}

// static
v8::Local<v8::Value> Converter<scoped_refptr<net::X509Certificate>>::ToV8(
    v8::Isolate* isolate, const scoped_refptr<net::X509Certificate>& val) {
  mate::Dictionary dict(isolate, v8::Object::New(isolate));
  std::string encoded_data;
  net::X509Certificate::GetPEMEncoded(
      val->os_cert_handle(), &encoded_data);

  dict.Set("data", encoded_data);
  dict.Set("issuer", val->issuer());
  dict.Set("issuerName", val->issuer().GetDisplayName());
  dict.Set("subject", val->subject());
  dict.Set("subjectName", val->subject().GetDisplayName());
  dict.Set("serialNumber", base::HexEncode(val->serial_number().data(),
                                           val->serial_number().size()));
  dict.Set("validStart", val->valid_start().ToDoubleT());
  dict.Set("validExpiry", val->valid_expiry().ToDoubleT());
  dict.Set("fingerprint",
           net::HashValue(
              val->CalculateFingerprint256(val->os_cert_handle())).ToString());

  if (!val->GetIntermediateCertificates().empty()) {
    net::X509Certificate::OSCertHandles issuer_intermediates(
        val->GetIntermediateCertificates().begin() + 1,
        val->GetIntermediateCertificates().end());
    const scoped_refptr<net::X509Certificate>& issuer_cert =
        net::X509Certificate::CreateFromHandle(
            val->GetIntermediateCertificates().front(),
            issuer_intermediates);
    dict.Set("issuerCert", issuer_cert);
  }

  return dict.GetHandle();
}

bool Converter<scoped_refptr<net::X509Certificate>>::FromV8(
    v8::Isolate* isolate, v8::Local<v8::Value> val,
    scoped_refptr<net::X509Certificate>* out) {
  mate::Dictionary dict;
  if (!ConvertFromV8(isolate, val, &dict))
    return false;

  std::string data;
  dict.Get("data", &data);
  scoped_refptr<net::X509Certificate> leaf_cert;
  if (!CertFromData(data, &leaf_cert))
    return false;

  scoped_refptr<net::X509Certificate> parent;
  if (dict.Get("issuerCert", &parent)) {
    auto parents = std::vector<net::X509Certificate::OSCertHandle>(
                      parent->GetIntermediateCertificates());
    parents.insert(parents.begin(), parent->os_cert_handle());
    auto cert = net::X509Certificate::CreateFromHandle(
      leaf_cert->os_cert_handle(), parents);
    if (!cert)
      return false;

    *out = cert;
  } else {
    *out = leaf_cert;
  }

  return true;
}

// static
v8::Local<v8::Value> Converter<net::CertPrincipal>::ToV8(
    v8::Isolate* isolate, const net::CertPrincipal& val) {
  mate::Dictionary dict(isolate, v8::Object::New(isolate));

  dict.Set("commonName", val.common_name);
  dict.Set("organizations", val.organization_names);
  dict.Set("organizationUnits", val.organization_unit_names);
  dict.Set("locality", val.locality_name);
  dict.Set("state", val.state_or_province_name);
  dict.Set("country", val.country_name);

  return dict.GetHandle();
}

// static
v8::Local<v8::Value> Converter<net::HttpResponseHeaders*>::ToV8(
    v8::Isolate* isolate,
    net::HttpResponseHeaders* headers) {
  base::DictionaryValue response_headers;
  if (headers) {
    size_t iter = 0;
    std::string key;
    std::string value;
    while (headers->EnumerateHeaderLines(&iter, &key, &value)) {
      key = base::ToLowerASCII(key);
      if (response_headers.HasKey(key)) {
        base::ListValue* values = nullptr;
        if (response_headers.GetList(key, &values))
          values->AppendString(value);
      } else {
        std::unique_ptr<base::ListValue> values(new base::ListValue());
        values->AppendString(value);
        response_headers.Set(key, std::move(values));
      }
    }
  }
  return ConvertToV8(isolate, response_headers);
}

}  // namespace mate

namespace atom {

void FillRequestDetails(base::DictionaryValue* details,
                        const net::URLRequest* request) {
  details->SetString("method", request->method());
  std::string url;
  if (!request->url_chain().empty()) url = request->url().spec();
  details->SetStringWithoutPathExpansion("url", url);
  details->SetString("referrer", request->referrer());
  std::unique_ptr<base::ListValue> list(new base::ListValue);
  GetUploadData(list.get(), request);
  if (!list->empty())
    details->Set("uploadData", std::move(list));
}

void GetUploadData(base::ListValue* upload_data_list,
                   const net::URLRequest* request) {
  const net::UploadDataStream* upload_data = request->get_upload();
  if (!upload_data)
    return;
  const std::vector<std::unique_ptr<net::UploadElementReader>>* readers =
      upload_data->GetElementReaders();
  for (const auto& reader : *readers) {
    std::unique_ptr<base::DictionaryValue> upload_data_dict(
        new base::DictionaryValue);
    if (reader->AsBytesReader()) {
      const net::UploadBytesElementReader* bytes_reader =
          reader->AsBytesReader();
      std::unique_ptr<base::Value> bytes(
          base::BinaryValue::CreateWithCopiedBuffer(bytes_reader->bytes(),
                                                    bytes_reader->length()));
      upload_data_dict->Set("bytes", std::move(bytes));
    } else if (reader->AsFileReader()) {
      const net::UploadFileElementReader* file_reader =
          reader->AsFileReader();
      auto file_path = file_reader->path().AsUTF8Unsafe();
      upload_data_dict->SetStringWithoutPathExpansion("file", file_path);
    } else {
      const storage::UploadBlobElementReader* blob_reader =
          static_cast<storage::UploadBlobElementReader*>(reader.get());
      upload_data_dict->SetString("blobUUID", blob_reader->uuid());
    }
    upload_data_list->Append(std::move(upload_data_dict));
  }
}

}  // namespace atom
<commit_msg>As you wish linter<commit_after>// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#include "atom/common/native_mate_converters/net_converter.h"

#include <string>
#include <vector>

#include "atom/common/native_mate_converters/gurl_converter.h"
#include "atom/common/native_mate_converters/value_converter.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "native_mate/dictionary.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/upload_data_stream.h"
#include "net/base/upload_element_reader.h"
#include "net/base/upload_file_element_reader.h"
#include "net/cert/x509_certificate.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "storage/browser/blob/upload_blob_element_reader.h"

#include "atom/common/node_includes.h"

namespace mate {

namespace {

bool CertFromData(const std::string& data,
    scoped_refptr<net::X509Certificate>* out) {
  auto cert_list = net::X509Certificate::CreateCertificateListFromBytes(
    data.c_str(), data.length(),
    net::X509Certificate::FORMAT_SINGLE_CERTIFICATE);
  if (cert_list.empty())
    return false;

  auto leaf_cert = cert_list.front();
  if (!leaf_cert)
    return false;

  *out = leaf_cert;

  return true;
}

}  // namespace

// static
v8::Local<v8::Value> Converter<const net::AuthChallengeInfo*>::ToV8(
    v8::Isolate* isolate, const net::AuthChallengeInfo* val) {
  mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate);
  dict.Set("isProxy", val->is_proxy);
  dict.Set("scheme", val->scheme);
  dict.Set("host", val->challenger.host());
  dict.Set("port", static_cast<uint32_t>(val->challenger.port()));
  dict.Set("realm", val->realm);
  return mate::ConvertToV8(isolate, dict);
}

// static
v8::Local<v8::Value> Converter<scoped_refptr<net::X509Certificate>>::ToV8(
    v8::Isolate* isolate, const scoped_refptr<net::X509Certificate>& val) {
  mate::Dictionary dict(isolate, v8::Object::New(isolate));
  std::string encoded_data;
  net::X509Certificate::GetPEMEncoded(
      val->os_cert_handle(), &encoded_data);

  dict.Set("data", encoded_data);
  dict.Set("issuer", val->issuer());
  dict.Set("issuerName", val->issuer().GetDisplayName());
  dict.Set("subject", val->subject());
  dict.Set("subjectName", val->subject().GetDisplayName());
  dict.Set("serialNumber", base::HexEncode(val->serial_number().data(),
                                           val->serial_number().size()));
  dict.Set("validStart", val->valid_start().ToDoubleT());
  dict.Set("validExpiry", val->valid_expiry().ToDoubleT());
  dict.Set("fingerprint",
           net::HashValue(
              val->CalculateFingerprint256(val->os_cert_handle())).ToString());

  if (!val->GetIntermediateCertificates().empty()) {
    net::X509Certificate::OSCertHandles issuer_intermediates(
        val->GetIntermediateCertificates().begin() + 1,
        val->GetIntermediateCertificates().end());
    const scoped_refptr<net::X509Certificate>& issuer_cert =
        net::X509Certificate::CreateFromHandle(
            val->GetIntermediateCertificates().front(),
            issuer_intermediates);
    dict.Set("issuerCert", issuer_cert);
  }

  return dict.GetHandle();
}

bool Converter<scoped_refptr<net::X509Certificate>>::FromV8(
    v8::Isolate* isolate, v8::Local<v8::Value> val,
    scoped_refptr<net::X509Certificate>* out) {
  mate::Dictionary dict;
  if (!ConvertFromV8(isolate, val, &dict))
    return false;

  std::string data;
  dict.Get("data", &data);
  scoped_refptr<net::X509Certificate> leaf_cert;
  if (!CertFromData(data, &leaf_cert))
    return false;

  scoped_refptr<net::X509Certificate> parent;
  if (dict.Get("issuerCert", &parent)) {
    auto parents = std::vector<net::X509Certificate::OSCertHandle>(
                      parent->GetIntermediateCertificates());
    parents.insert(parents.begin(), parent->os_cert_handle());
    auto cert = net::X509Certificate::CreateFromHandle(
      leaf_cert->os_cert_handle(), parents);
    if (!cert)
      return false;

    *out = cert;
  } else {
    *out = leaf_cert;
  }

  return true;
}

// static
v8::Local<v8::Value> Converter<net::CertPrincipal>::ToV8(
    v8::Isolate* isolate, const net::CertPrincipal& val) {
  mate::Dictionary dict(isolate, v8::Object::New(isolate));

  dict.Set("commonName", val.common_name);
  dict.Set("organizations", val.organization_names);
  dict.Set("organizationUnits", val.organization_unit_names);
  dict.Set("locality", val.locality_name);
  dict.Set("state", val.state_or_province_name);
  dict.Set("country", val.country_name);

  return dict.GetHandle();
}

// static
v8::Local<v8::Value> Converter<net::HttpResponseHeaders*>::ToV8(
    v8::Isolate* isolate,
    net::HttpResponseHeaders* headers) {
  base::DictionaryValue response_headers;
  if (headers) {
    size_t iter = 0;
    std::string key;
    std::string value;
    while (headers->EnumerateHeaderLines(&iter, &key, &value)) {
      key = base::ToLowerASCII(key);
      if (response_headers.HasKey(key)) {
        base::ListValue* values = nullptr;
        if (response_headers.GetList(key, &values))
          values->AppendString(value);
      } else {
        std::unique_ptr<base::ListValue> values(new base::ListValue());
        values->AppendString(value);
        response_headers.Set(key, std::move(values));
      }
    }
  }
  return ConvertToV8(isolate, response_headers);
}

}  // namespace mate

namespace atom {

void FillRequestDetails(base::DictionaryValue* details,
                        const net::URLRequest* request) {
  details->SetString("method", request->method());
  std::string url;
  if (!request->url_chain().empty()) url = request->url().spec();
  details->SetStringWithoutPathExpansion("url", url);
  details->SetString("referrer", request->referrer());
  std::unique_ptr<base::ListValue> list(new base::ListValue);
  GetUploadData(list.get(), request);
  if (!list->empty())
    details->Set("uploadData", std::move(list));
}

void GetUploadData(base::ListValue* upload_data_list,
                   const net::URLRequest* request) {
  const net::UploadDataStream* upload_data = request->get_upload();
  if (!upload_data)
    return;
  const std::vector<std::unique_ptr<net::UploadElementReader>>* readers =
      upload_data->GetElementReaders();
  for (const auto& reader : *readers) {
    std::unique_ptr<base::DictionaryValue> upload_data_dict(
        new base::DictionaryValue);
    if (reader->AsBytesReader()) {
      const net::UploadBytesElementReader* bytes_reader =
          reader->AsBytesReader();
      std::unique_ptr<base::Value> bytes(
          base::BinaryValue::CreateWithCopiedBuffer(bytes_reader->bytes(),
                                                    bytes_reader->length()));
      upload_data_dict->Set("bytes", std::move(bytes));
    } else if (reader->AsFileReader()) {
      const net::UploadFileElementReader* file_reader =
          reader->AsFileReader();
      auto file_path = file_reader->path().AsUTF8Unsafe();
      upload_data_dict->SetStringWithoutPathExpansion("file", file_path);
    } else {
      const storage::UploadBlobElementReader* blob_reader =
          static_cast<storage::UploadBlobElementReader*>(reader.get());
      upload_data_dict->SetString("blobUUID", blob_reader->uuid());
    }
    upload_data_list->Append(std::move(upload_data_dict));
  }
}

}  // namespace atom
<|endoftext|>


<commit_before>#include "SkeletonSmoother.h"

SkeletonSmoother::SkeletonSmoother(ICoordinateMapper *coordinateMapper)
    : m_CoordinateMapper(coordinateMapper)
    , m_SmoothScale(1.f)
    , m_PositionScale(1.f)
{
    std::fill(m_JointPositions.at(0).begin(), m_JointPositions.at(0).end(), JointProp());
    std::fill(m_JointPositions.at(1).begin(), m_JointPositions.at(1).end(), JointProp());
    std::fill(m_JointPositions.at(2).begin(), m_JointPositions.at(2).end(), JointProp());
    std::fill(m_JointPositions.at(3).begin(), m_JointPositions.at(3).end(), JointProp());
    std::fill(m_JointPositions.at(4).begin(), m_JointPositions.at(4).end(), JointProp());
    std::fill(m_JointPositions.at(5).begin(), m_JointPositions.at(5).end(), JointProp());
}

void SkeletonSmoother::updateJointPositions(const unsigned int &bodyIndex, const float &delta, const PointF &screenSize, Joint *joints)
{
    for (unsigned int jointIndex = 0; jointIndex < JointType_Count; jointIndex++) {
        const PointF jointRawPos = mapBodyPointToScreenPoint(joints[jointIndex].Position, screenSize.X, screenSize.Y);
        //Invert the Y-axis
        const PointF screenPos = {jointRawPos.X, screenSize.Y - jointRawPos.Y};
        JointArray &jointPositions = m_JointPositions.at(bodyIndex);
        JointProp &prop = jointPositions.at(jointIndex);
        if (pointEquals(prop.pos, pointZero()) || prop.isDirty) {
            prop.pos.X = screenPos.X * m_PositionScale;
            prop.pos.Y = screenPos.Y * m_PositionScale;
            prop.isDirty = false;
        }
        prop.attractionPoint.X = screenPos.X * m_PositionScale;
        prop.attractionPoint.Y = screenPos.Y * m_PositionScale;
    }

    for (unsigned int jointIndex = 0; jointIndex < m_JointPositions.at(bodyIndex).size(); jointIndex++) {
        m_JointPositions.at(bodyIndex).at(jointIndex).updatePos(delta, m_SmoothScale);
    }
}

void SkeletonSmoother::reset(const unsigned int &bodyIndex)
{
    for (unsigned int i = 0; i < JointType_Count; i++) {
        m_JointPositions.at(bodyIndex).at(i).reset();
    }
}

void SkeletonSmoother::setSmoothScale(float scale)
{
    if (scale <= 0) {
        return;
    }

    m_SmoothScale = scale;
}

float SkeletonSmoother::getSmoothScale() const
{
    return m_SmoothScale;
}

void SkeletonSmoother::setPositionScale(float scale)
{
    if (scale <= 0) {
        return;
    }

    m_PositionScale = scale;
    for (unsigned int bodyIndex = 0; bodyIndex < m_JointPositions.size(); bodyIndex++) {
        for (unsigned int i = 0; i < JointType_Count; i++) {
            m_JointPositions.at(bodyIndex).at(i).isDirty = true;
        }
    }
}

float SkeletonSmoother::getPositionScale() const
{
    return m_PositionScale;
}

const std::array<SkeletonSmoother::JointProp, JointType_Count> &SkeletonSmoother::getJointProperties(const unsigned int &bodyIndex) const
{
    return m_JointPositions.at(bodyIndex);
}

PointF SkeletonSmoother::getJointPosition(const unsigned int &bodyIndex, const unsigned int &type) const
{
    return m_JointPositions.at(bodyIndex).at(type).pos;
}

void SkeletonSmoother::enableJointDrawing(unsigned int bodyIndex, JointType jointType, bool enable)
{
    m_JointPositions.at(bodyIndex).at(jointType).isDraw = enable;
}

bool SkeletonSmoother::isJointDrew(unsigned int bodyIndex, JointType jointType) const
{
    return m_JointPositions.at(bodyIndex).at(jointType).isDraw;
}

PointF SkeletonSmoother::mapBodyPointToScreenPoint(const CameraSpacePoint &bodyPoint, const int &width, const int &height)
{
    // Calculate the body's position on the screen
    PointF screenPos = {0, 0};
    DepthSpacePoint depthPoint = {0, 0};

    if (m_CoordinateMapper) {
        m_CoordinateMapper->MapCameraPointToDepthSpace(bodyPoint, &depthPoint);
        screenPos.X = static_cast<float>(depthPoint.X * width) / SkeletonSmoother::DEPTH_WIDTH;
        screenPos.Y = static_cast<float>(depthPoint.Y * height) / SkeletonSmoother::DEPTH_HEIGHT;
    }

    return screenPos;
}

bool SkeletonSmoother::pointEquals(const PointF &p1, const PointF &p2)
{
    return (p1.X == p2.X) && (p1.Y == p2.Y);
}

PointF SkeletonSmoother::pointZero()
{
    PointF p = {0, 0};
    return p;
}
<commit_msg>Check for nullptr and body index validity<commit_after>#include "SkeletonSmoother.h"

SkeletonSmoother::SkeletonSmoother(ICoordinateMapper *coordinateMapper)
    : m_CoordinateMapper(coordinateMapper)
    , m_SmoothScale(1.f)
    , m_PositionScale(1.f)
{
    std::fill(m_JointPositions.at(0).begin(), m_JointPositions.at(0).end(), JointProp());
    std::fill(m_JointPositions.at(1).begin(), m_JointPositions.at(1).end(), JointProp());
    std::fill(m_JointPositions.at(2).begin(), m_JointPositions.at(2).end(), JointProp());
    std::fill(m_JointPositions.at(3).begin(), m_JointPositions.at(3).end(), JointProp());
    std::fill(m_JointPositions.at(4).begin(), m_JointPositions.at(4).end(), JointProp());
    std::fill(m_JointPositions.at(5).begin(), m_JointPositions.at(5).end(), JointProp());
}

void SkeletonSmoother::updateJointPositions(const unsigned int &bodyIndex, const float &delta, const PointF &screenSize, Joint *joints)
{
    if (joints == nullptr || bodyIndex >= BODY_COUNT) {
        return;
    }

    for (unsigned int jointIndex = 0; jointIndex < JointType_Count; jointIndex++) {
        const PointF jointRawPos = mapBodyPointToScreenPoint(joints[jointIndex].Position, screenSize.X, screenSize.Y);
        //Invert the Y-axis
        const PointF screenPos = {jointRawPos.X, screenSize.Y - jointRawPos.Y};
        JointArray &jointPositions = m_JointPositions.at(bodyIndex);
        JointProp &prop = jointPositions.at(jointIndex);
        if (pointEquals(prop.pos, pointZero()) || prop.isDirty) {
            prop.pos.X = screenPos.X * m_PositionScale;
            prop.pos.Y = screenPos.Y * m_PositionScale;
            prop.isDirty = false;
        }
        prop.attractionPoint.X = screenPos.X * m_PositionScale;
        prop.attractionPoint.Y = screenPos.Y * m_PositionScale;
    }

    for (unsigned int jointIndex = 0; jointIndex < m_JointPositions.at(bodyIndex).size(); jointIndex++) {
        m_JointPositions.at(bodyIndex).at(jointIndex).updatePos(delta, m_SmoothScale);
    }
}

void SkeletonSmoother::reset(const unsigned int &bodyIndex)
{
    for (unsigned int i = 0; i < JointType_Count; i++) {
        m_JointPositions.at(bodyIndex).at(i).reset();
    }
}

void SkeletonSmoother::setSmoothScale(float scale)
{
    if (scale <= 0) {
        return;
    }

    m_SmoothScale = scale;
}

float SkeletonSmoother::getSmoothScale() const
{
    return m_SmoothScale;
}

void SkeletonSmoother::setPositionScale(float scale)
{
    if (scale <= 0) {
        return;
    }

    m_PositionScale = scale;
    for (unsigned int bodyIndex = 0; bodyIndex < m_JointPositions.size(); bodyIndex++) {
        for (unsigned int i = 0; i < JointType_Count; i++) {
            m_JointPositions.at(bodyIndex).at(i).isDirty = true;
        }
    }
}

float SkeletonSmoother::getPositionScale() const
{
    return m_PositionScale;
}

const std::array<SkeletonSmoother::JointProp, JointType_Count> &SkeletonSmoother::getJointProperties(const unsigned int &bodyIndex) const
{
    return m_JointPositions.at(bodyIndex);
}

PointF SkeletonSmoother::getJointPosition(const unsigned int &bodyIndex, const unsigned int &type) const
{
    return m_JointPositions.at(bodyIndex).at(type).pos;
}

void SkeletonSmoother::enableJointDrawing(unsigned int bodyIndex, JointType jointType, bool enable)
{
    m_JointPositions.at(bodyIndex).at(jointType).isDraw = enable;
}

bool SkeletonSmoother::isJointDrew(unsigned int bodyIndex, JointType jointType) const
{
    return m_JointPositions.at(bodyIndex).at(jointType).isDraw;
}

PointF SkeletonSmoother::mapBodyPointToScreenPoint(const CameraSpacePoint &bodyPoint, const int &width, const int &height)
{
    // Calculate the body's position on the screen
    PointF screenPos = {0, 0};
    DepthSpacePoint depthPoint = {0, 0};

    if (m_CoordinateMapper) {
        m_CoordinateMapper->MapCameraPointToDepthSpace(bodyPoint, &depthPoint);
        screenPos.X = static_cast<float>(depthPoint.X * width) / SkeletonSmoother::DEPTH_WIDTH;
        screenPos.Y = static_cast<float>(depthPoint.Y * height) / SkeletonSmoother::DEPTH_HEIGHT;
    }

    return screenPos;
}

bool SkeletonSmoother::pointEquals(const PointF &p1, const PointF &p2)
{
    return (p1.X == p2.X) && (p1.Y == p2.Y);
}

PointF SkeletonSmoother::pointZero()
{
    PointF p = {0, 0};
    return p;
}
<|endoftext|>


<commit_before>/*
 * FileLock.cpp
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include <core/FileLock.hpp>

// #define RSTUDIO_ENABLE_DEBUG_MACROS
#include <core/Macros.hpp>

#include <core/Settings.hpp>
#include <core/Error.hpp>
#include <core/Log.hpp>
#include <core/FileSerializer.hpp>

#include <boost/algorithm/string.hpp>

namespace rstudio {
namespace core {

namespace file_lock {
void initialize()
{
   FileLock::initialize();
}
} // end namespace file_lock

namespace {

const char * const kLocksConfPath = "/etc/rstudio/locks.conf";
#define kDefaultRefreshRate     20.0
#define kDefaultTimeoutInterval 30.0

std::string lockTypeToString(FileLock::LockType type)
{
   switch (type)
   {
   case FileLock::LOCKTYPE_ADVISORY:  return "advisory";
   case FileLock::LOCKTYPE_LINKBASED: return "linkbased";
   }

   // not reached
   return std::string();
}

FileLock::LockType stringToLockType(const std::string& lockType)
{
   using namespace boost::algorithm;

   if (boost::iequals(lockType, "advisory"))
      return FileLock::LOCKTYPE_ADVISORY;
   else if (boost::iequals(lockType, "linkbased"))
      return FileLock::LOCKTYPE_LINKBASED;

   LOG_WARNING_MESSAGE("unrecognized lock type '" + lockType + "'");
   return FileLock::LOCKTYPE_ADVISORY;
}

double getFieldPositive(const Settings& settings,
                        const std::string& name,
                        double defaultValue)
{
   double value = settings.getDouble(name, defaultValue);
   if (value < 0)
   {
      LOG_WARNING_MESSAGE("invalid field '" + name + "': must be positive");
      return defaultValue;
   }

   return value;
}

} // end anonymous namespace

bool s_isInitialized = false;

void FileLock::ensureInitialized()
{
   if (s_isInitialized)
      return;

   FileLock::initialize();
}

void FileLock::initialize(FilePath locksConfPath)
{
   s_isInitialized = true;

   if (locksConfPath.empty())
      locksConfPath = FilePath(kLocksConfPath);

   if (!locksConfPath.exists())
      return;

   Settings settings;
   Error error = settings.initialize(locksConfPath);
   if (error)
   {
      LOG_ERROR(error);
      return;
   }

   FileLock::initialize(settings);
}

void FileLock::initialize(const Settings& settings)
{
   s_isInitialized = true;

   // default lock type
   FileLock::s_defaultType = stringToLockType(settings.get("lock-type", "advisory"));

   // timeout interval
   double timeoutInterval = getFieldPositive(settings, "timeout-interval", kDefaultTimeoutInterval);
   FileLock::s_timeoutInterval = boost::posix_time::seconds(timeoutInterval);

   // refresh rate
   double refreshRate = getFieldPositive(settings, "refresh-rate", kDefaultRefreshRate);
   FileLock::s_refreshRate = boost::posix_time::seconds(refreshRate);

   DEBUG_BLOCK("lock initialization")
   {
      std::cerr << "Type: "    << lockTypeToString(FileLock::s_defaultType) << std::endl;
      std::cerr << "Timeout: " << FileLock::s_timeoutInterval.total_seconds() << std::endl;
      std::cerr << "Refresh: " << FileLock::s_refreshRate.total_seconds() << std::endl;
   }
}

// default values for static members
FileLock::LockType FileLock::s_defaultType(FileLock::LOCKTYPE_ADVISORY);
boost::posix_time::seconds FileLock::s_timeoutInterval(kDefaultTimeoutInterval);
boost::posix_time::seconds FileLock::s_refreshRate(kDefaultRefreshRate);

boost::shared_ptr<FileLock> FileLock::create(LockType type)
{
   switch (type)
   {
   case LOCKTYPE_ADVISORY:  return boost::shared_ptr<FileLock>(new AdvisoryFileLock());
   case LOCKTYPE_LINKBASED: return boost::shared_ptr<FileLock>(new LinkBasedFileLock());
   }

   // shouldn't be reached
   return boost::shared_ptr<FileLock>(new AdvisoryFileLock());
}

boost::shared_ptr<FileLock> FileLock::createDefault()
{
   return FileLock::create(s_defaultType);
}

void FileLock::refresh()
{
   AdvisoryFileLock::refresh();
   LinkBasedFileLock::refresh();
}

void FileLock::cleanUp()
{
   AdvisoryFileLock::cleanUp();
   LinkBasedFileLock::cleanUp();
}

namespace {

void schedulePeriodicExecution(
      const boost::system::error_code& ec,
      boost::asio::deadline_timer& timer,
      boost::posix_time::seconds interval,
      boost::function<void()> callback)
{
   try
   {
      // bail on boost errors (these are very unexpected)
      if (ec)
      {
         LOG_ERROR(core::Error(ec, ERROR_LOCATION));
         return;
      }

      // execute callback
      callback();

      // reschedule
      boost::system::error_code errc;
      timer.expires_at(timer.expires_at() + interval, errc);
      if (errc)
      {
         LOG_ERROR(Error(errc, ERROR_LOCATION));
         return;
      }

      timer.async_wait(boost::bind(
                          schedulePeriodicExecution,
                          boost::asio::placeholders::error,
                          boost::ref(timer),
                          interval,
                          callback));
   }
   catch (...)
   {
      // swallow errors
   }
}

} // end anonymous namespace

void FileLock::refreshPeriodically(boost::asio::io_service& service,
                                   boost::posix_time::seconds interval)
{
   // protect against re-entrancy
   static bool s_isRefreshing = false;
   if (s_isRefreshing)
      return;
   s_isRefreshing = true;

   static boost::asio::deadline_timer timer(service, interval);
   timer.async_wait(boost::bind(
                       schedulePeriodicExecution,
                       boost::asio::placeholders::error,
                       boost::ref(timer),
                       interval,
                       FileLock::refresh));
}

} // end namespace core
} // end namespace rstudio
<commit_msg>follow naming convention (file-locks)<commit_after>/*
 * FileLock.cpp
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include <core/FileLock.hpp>

// #define RSTUDIO_ENABLE_DEBUG_MACROS
#include <core/Macros.hpp>

#include <core/Settings.hpp>
#include <core/Error.hpp>
#include <core/Log.hpp>
#include <core/FileSerializer.hpp>

#include <boost/algorithm/string.hpp>

namespace rstudio {
namespace core {

namespace file_lock {
void initialize()
{
   FileLock::initialize();
}
} // end namespace file_lock

namespace {

const char * const kLocksConfPath = "/etc/rstudio/file-locks";
#define kDefaultRefreshRate     20.0
#define kDefaultTimeoutInterval 30.0

std::string lockTypeToString(FileLock::LockType type)
{
   switch (type)
   {
   case FileLock::LOCKTYPE_ADVISORY:  return "advisory";
   case FileLock::LOCKTYPE_LINKBASED: return "linkbased";
   }

   // not reached
   return std::string();
}

FileLock::LockType stringToLockType(const std::string& lockType)
{
   using namespace boost::algorithm;

   if (boost::iequals(lockType, "advisory"))
      return FileLock::LOCKTYPE_ADVISORY;
   else if (boost::iequals(lockType, "linkbased"))
      return FileLock::LOCKTYPE_LINKBASED;

   LOG_WARNING_MESSAGE("unrecognized lock type '" + lockType + "'");
   return FileLock::LOCKTYPE_ADVISORY;
}

double getFieldPositive(const Settings& settings,
                        const std::string& name,
                        double defaultValue)
{
   double value = settings.getDouble(name, defaultValue);
   if (value < 0)
   {
      LOG_WARNING_MESSAGE("invalid field '" + name + "': must be positive");
      return defaultValue;
   }

   return value;
}

} // end anonymous namespace

bool s_isInitialized = false;

void FileLock::ensureInitialized()
{
   if (s_isInitialized)
      return;

   FileLock::initialize();
}

void FileLock::initialize(FilePath locksConfPath)
{
   s_isInitialized = true;

   if (locksConfPath.empty())
      locksConfPath = FilePath(kLocksConfPath);

   if (!locksConfPath.exists())
      return;

   Settings settings;
   Error error = settings.initialize(locksConfPath);
   if (error)
   {
      LOG_ERROR(error);
      return;
   }

   FileLock::initialize(settings);
}

void FileLock::initialize(const Settings& settings)
{
   s_isInitialized = true;

   // default lock type
   FileLock::s_defaultType = stringToLockType(settings.get("lock-type", "advisory"));

   // timeout interval
   double timeoutInterval = getFieldPositive(settings, "timeout-interval", kDefaultTimeoutInterval);
   FileLock::s_timeoutInterval = boost::posix_time::seconds(timeoutInterval);

   // refresh rate
   double refreshRate = getFieldPositive(settings, "refresh-rate", kDefaultRefreshRate);
   FileLock::s_refreshRate = boost::posix_time::seconds(refreshRate);

   DEBUG_BLOCK("lock initialization")
   {
      std::cerr << "Type: "    << lockTypeToString(FileLock::s_defaultType) << std::endl;
      std::cerr << "Timeout: " << FileLock::s_timeoutInterval.total_seconds() << std::endl;
      std::cerr << "Refresh: " << FileLock::s_refreshRate.total_seconds() << std::endl;
   }
}

// default values for static members
FileLock::LockType FileLock::s_defaultType(FileLock::LOCKTYPE_ADVISORY);
boost::posix_time::seconds FileLock::s_timeoutInterval(kDefaultTimeoutInterval);
boost::posix_time::seconds FileLock::s_refreshRate(kDefaultRefreshRate);

boost::shared_ptr<FileLock> FileLock::create(LockType type)
{
   switch (type)
   {
   case LOCKTYPE_ADVISORY:  return boost::shared_ptr<FileLock>(new AdvisoryFileLock());
   case LOCKTYPE_LINKBASED: return boost::shared_ptr<FileLock>(new LinkBasedFileLock());
   }

   // shouldn't be reached
   return boost::shared_ptr<FileLock>(new AdvisoryFileLock());
}

boost::shared_ptr<FileLock> FileLock::createDefault()
{
   return FileLock::create(s_defaultType);
}

void FileLock::refresh()
{
   AdvisoryFileLock::refresh();
   LinkBasedFileLock::refresh();
}

void FileLock::cleanUp()
{
   AdvisoryFileLock::cleanUp();
   LinkBasedFileLock::cleanUp();
}

namespace {

void schedulePeriodicExecution(
      const boost::system::error_code& ec,
      boost::asio::deadline_timer& timer,
      boost::posix_time::seconds interval,
      boost::function<void()> callback)
{
   try
   {
      // bail on boost errors (these are very unexpected)
      if (ec)
      {
         LOG_ERROR(core::Error(ec, ERROR_LOCATION));
         return;
      }

      // execute callback
      callback();

      // reschedule
      boost::system::error_code errc;
      timer.expires_at(timer.expires_at() + interval, errc);
      if (errc)
      {
         LOG_ERROR(Error(errc, ERROR_LOCATION));
         return;
      }

      timer.async_wait(boost::bind(
                          schedulePeriodicExecution,
                          boost::asio::placeholders::error,
                          boost::ref(timer),
                          interval,
                          callback));
   }
   catch (...)
   {
      // swallow errors
   }
}

} // end anonymous namespace

void FileLock::refreshPeriodically(boost::asio::io_service& service,
                                   boost::posix_time::seconds interval)
{
   // protect against re-entrancy
   static bool s_isRefreshing = false;
   if (s_isRefreshing)
      return;
   s_isRefreshing = true;

   static boost::asio::deadline_timer timer(service, interval);
   timer.async_wait(boost::bind(
                       schedulePeriodicExecution,
                       boost::asio::placeholders::error,
                       boost::ref(timer),
                       interval,
                       FileLock::refresh));
}

} // end namespace core
} // end namespace rstudio
<|endoftext|>