<commit_before>/*
* Copyright (c) 2012-2020 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) {

/////////////////////////////////////////////////////////////////////////////////////////////
// Handle the output tasks
            VSFrameContext *frameContext = iter->get();
            VSNode *node = frameContext->key.first;

            // FIXME, somewhat ugly code duplication and is effectively a success only version of the notification at the end
            // Fast exit path for checking cache
            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;
                }
            }

            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;

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

            // 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 (filterMode == fmFrameState && frameProcessingDone)
                    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_back(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 (/*!context->upstreamContext && */++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));
        tasks.push_back(ctx);
    }

    wakeThread();
}

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 comments<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);

                    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 (filterMode == fmFrameState && frameProcessingDone)
                    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_back(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 (/*!context->upstreamContext && */++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));
        tasks.push_back(ctx);
    }

    wakeThread();
}

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 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"

VSThread::VSThread(VSThreadPool *owner) : owner(owner), stop(false) {

}

void VSThread::stopThread() {
    stop = true;
}

void VSThread::run() {
    owner->lock.lock();
    owner->activeThreads++;

    while (true) {
        bool ranTask = false;

        for (int i = 0; i < owner->tasks.count(); i++) {
            PFrameContext rCtx = owner->tasks[i];

            if (rCtx->frameDone && rCtx->returnedFrame) {
                owner->tasks.removeAt(i--);
                owner->returnFrame(rCtx, rCtx->returnedFrame);
                ranTask = true;
                break;
            } else {

                PFrameContext pCtx = rCtx;

                if (rCtx->returnedFrame || rCtx->hasError())
                    rCtx = rCtx->upstreamContext;

                Q_ASSERT(rCtx);
                Q_ASSERT(pCtx);

                // if an error has already been flagged upstream simply drop this task so a filter won't get multiple arError calls for the same frame
                if (rCtx->hasError()) {
                    owner->tasks.removeAt(i--);
                    continue;
                }

                bool isSingleInstance = (rCtx->clip->filterMode == fmParallelRequests && pCtx->returnedFrame && rCtx->numFrameRequests == 1 && pCtx != rCtx) || rCtx->clip->filterMode == fmSerial;

                // this check is common for both filter modes, it makes sure that multiple calls won't be made in parallel to a single filter to produce the same frame
                // special casing so serial unordered doesn't need yet another list
                if (owner->runningTasks.contains(FrameKey(rCtx->clip, rCtx->clip->filterMode == fmUnordered ? -1 : rCtx->n)))
                    continue;

                if (isSingleInstance) {
                    // this is the complicated case, a new frame may not be started until all calls are completed for the current one
                    if (owner->framesInProgress.contains(rCtx->clip) && owner->framesInProgress[rCtx->clip] != rCtx->n)
                        continue;
                }

                // mark task as active
                owner->tasks.removeAt(i--);
                owner->runningTasks.insert(FrameKey(rCtx->clip, rCtx->clip->filterMode == fmUnordered ? -1 : rCtx->n), rCtx);

                if (isSingleInstance)
                    owner->framesInProgress.insert(rCtx->clip, rCtx->n);

                ActivationReason ar = arInitial;

                if (pCtx->hasError()) {
                    ar = arError;
                    rCtx->setError(pCtx->getErrorMessage());
                } else if (pCtx != rCtx && pCtx->returnedFrame) {
                    if (--rCtx->numFrameRequests)
                        ar = arFrameReady;
                    else
                        ar = arAllFramesReady;

                    Q_ASSERT(rCtx->numFrameRequests >= 0);
                    rCtx->availableFrames.insert(NodeOutputKey(pCtx->clip, pCtx->n, pCtx->index), pCtx->returnedFrame);
                    rCtx->lastCompletedN = pCtx->n;
                    rCtx->lastCompletedNode = pCtx->node;
                }

                owner->lock.unlock();
                // run task

                PVideoFrame f = rCtx->clip->getFrameInternal(rCtx->n, ar, rCtx);
                ranTask = true;
                owner->lock.lock();

                if (f && rCtx->numFrameRequests > 0)
                    qFatal("Frame returned but there are still pending frame requests, filter: " + rCtx->clip->name);

                owner->runningTasks.remove(FrameKey(rCtx->clip, rCtx->clip->filterMode == fmUnordered ? -1 : rCtx->n));

				if (f || ar == arError || rCtx->hasError()) {
                    // free all input frames quickly since the frame processing is done
                    rCtx->availableFrames.clear();

                    if (isSingleInstance) {
                        if (owner->framesInProgress[rCtx->clip] != rCtx->n && !rCtx->hasError())
                            qWarning("Releasing unobtained frame lock");

                        owner->framesInProgress.remove(rCtx->clip);
                    }

                    owner->allContexts.remove(NodeOutputKey(rCtx->clip, rCtx->n, rCtx->index));
                }

                if (rCtx->hasError()) {
                    PFrameContext n;

                    do {
                        n = rCtx->notificationChain;

                        if (n) {
                            rCtx->notificationChain.clear();
                            n->setError(rCtx->getErrorMessage());
                        }

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

                        if (rCtx->frameDone) {
                            owner->lock.unlock();
                            QMutexLocker callbackLock(&owner->callbackLock);
                            rCtx->frameDone(rCtx->userData, NULL, rCtx->n, rCtx->node, rCtx->getErrorMessage().constData());
                            callbackLock.unlock();
                            owner->lock.lock();
                        }
                    } while ((rCtx = n));
                } else if (f) {
                    Q_ASSERT(rCtx->numFrameRequests == 0);
                    PFrameContext n;

                    do {
                        n = rCtx->notificationChain;

                        if (n)
                            rCtx->notificationChain.clear();

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

                        if (rCtx->frameDone)
                            owner->returnFrame(rCtx, f);
                    } while ((rCtx = n));
                } else if (rCtx->numFrameRequests > 0 || rCtx->n < 0) {
                    // already scheduled or in the case of negative n it is simply a cache notify message
                } else {
                    qFatal("No frame returned at the end of processing by " + rCtx->clip->name);
                }

                break;
            }
        }

        if (!ranTask && !stop) {
            owner->activeThreads--;
            owner->newWork.wait(&owner->lock);
            owner->activeThreads++;
        } else if (stop) {
            owner->activeThreads--;
            owner->lock.unlock();
            return;
        }
    }
}

VSThreadPool::VSThreadPool(VSCore *core, int threads) : core(core), activeThreads(0) {
    setThreadCount(threads > 0 ? threads : QThread::idealThreadCount());
}

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

int	VSThreadPool::threadCount() const {
    return allThreads.count();
}

void VSThreadPool::setThreadCount(int threadCount) {
    QMutexLocker m(&lock);

    while (threadCount > allThreads.count()) {
        VSThread *vst = new VSThread(this);
        allThreads.insert(vst);
        vst->start();
    }

    while (threadCount < allThreads.count()) {
        VSThread *t = *allThreads.begin();
        t->stopThread();
        newWork.wakeAll();
        m.unlock();
        t->wait();
        m.relock();
        allThreads.remove(t);
        delete t;
        newWork.wakeAll();
    }
}

void VSThreadPool::wakeThread() {
    if (activeThreads < threadCount())
        newWork.wakeOne();
}

void VSThreadPool::releaseThread() {
    setThreadCount(allThreads.count() + 1);
}

void VSThreadPool::reserveThread() {
    setThreadCount(allThreads.count() - 1);
}

void VSThreadPool::notifyCaches(CacheActivation reason) {
    for (int i = 0; i < core->caches.count(); i++)
        tasks.insert(0, PFrameContext(new FrameContext(reason, 0, core->caches[i], PFrameContext())));
}

void VSThreadPool::start(const PFrameContext &context) {
    Q_ASSERT(context);
    QMutexLocker m(&lock);
    startInternal(context);
}

void VSThreadPool::returnFrame(const PFrameContext &rCtx, const PVideoFrame &f) {
    Q_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);
    QMutexLocker m(&callbackLock);
    rCtx->frameDone(rCtx->userData, ref, rCtx->n, rCtx->node, NULL);
    m.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)
        qFatal("Negative frame request by: " + context->clip->name);

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

    // a normal tick for caches to adjust their sizes based on recent history
    if (!context->upstreamContext && ticks.fetchAndAddAcquire(1) == 99) {
        ticks = 0;
        notifyCaches(cCacheTick);
    }

    // add it immediately if the task is to return a completed frame
    if (context->returnedFrame) {
        tasks.append(context);
        wakeThread();
        return;
    } else {
        if (context->upstreamContext)
            context->upstreamContext->numFrameRequests++;

        ////////////////////////
        // see if the task is a duplicate
        foreach(const PFrameContext &ctx, tasks) {
            if (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.append(context);
                    wakeThread();
                    return;
                } else {
                    PFrameContext rCtx = ctx;

                    if (rCtx->returnedFrame)
                        rCtx = rCtx->upstreamContext;

                    if (context->clip == rCtx->clip && context->n == rCtx->n && context->index == ctx->index) {
                        PFrameContext t = rCtx;

                        while (t && t->notificationChain)
                            t = t->notificationChain;

                        t->notificationChain = context;
                        return;
                    }
                }
            }
        }

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

        if (allContexts.contains(p)) {
            PFrameContext ctx = allContexts[p];
            Q_ASSERT(ctx);
            Q_ASSERT(context->clip == ctx->clip && context->n == ctx->n);

            if (ctx->returnedFrame) {
                // special case where the requested frame is encountered "by accident"
                context->returnedFrame = ctx->returnedFrame;
                tasks.append(context);
                wakeThread();
                return;
            } else {
                while (ctx->notificationChain)
                    ctx = ctx->notificationChain;
                ctx->notificationChain = context;
                return;
            }
        } else {
            allContexts[p] = context;
        }

        tasks.append(context);
        wakeThread();
        return;
    }

}

void VSThreadPool::waitForDone() {

}

VSThreadPool::~VSThreadPool() {
    setThreadCount(0);
};
<commit_msg>Remove duplicate commented line<commit_after>/*
* Copyright (c) 2012 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"

VSThread::VSThread(VSThreadPool *owner) : owner(owner), stop(false) {

}

void VSThread::stopThread() {
    stop = true;
}

void VSThread::run() {
    owner->lock.lock();
    owner->activeThreads++;

    while (true) {
        bool ranTask = false;

        for (int i = 0; i < owner->tasks.count(); i++) {
            PFrameContext rCtx = owner->tasks[i];

            if (rCtx->frameDone && rCtx->returnedFrame) {
                owner->tasks.removeAt(i--);
                owner->returnFrame(rCtx, rCtx->returnedFrame);
                ranTask = true;
                break;
            } else {

                PFrameContext pCtx = rCtx;

                if (rCtx->returnedFrame || rCtx->hasError())
                    rCtx = rCtx->upstreamContext;

                Q_ASSERT(rCtx);
                Q_ASSERT(pCtx);

                // if an error has already been flagged upstream simply drop this task so a filter won't get multiple arError calls for the same frame
                if (rCtx->hasError()) {
                    owner->tasks.removeAt(i--);
                    continue;
                }

                bool isSingleInstance = (rCtx->clip->filterMode == fmParallelRequests && pCtx->returnedFrame && rCtx->numFrameRequests == 1 && pCtx != rCtx) || rCtx->clip->filterMode == fmSerial;

                // this check is common for both filter modes, it makes sure that multiple calls won't be made in parallel to a single filter to produce the same frame
                // special casing so serial unordered doesn't need yet another list
                if (owner->runningTasks.contains(FrameKey(rCtx->clip, rCtx->clip->filterMode == fmUnordered ? -1 : rCtx->n)))
                    continue;

                if (isSingleInstance) {
                    // this is the complicated case, a new frame may not be started until all calls are completed for the current one
                    if (owner->framesInProgress.contains(rCtx->clip) && owner->framesInProgress[rCtx->clip] != rCtx->n)
                        continue;
                }

                // mark task as active
                owner->tasks.removeAt(i--);
                owner->runningTasks.insert(FrameKey(rCtx->clip, rCtx->clip->filterMode == fmUnordered ? -1 : rCtx->n), rCtx);

                if (isSingleInstance)
                    owner->framesInProgress.insert(rCtx->clip, rCtx->n);

                ActivationReason ar = arInitial;

                if (pCtx->hasError()) {
                    ar = arError;
                    rCtx->setError(pCtx->getErrorMessage());
                } else if (pCtx != rCtx && pCtx->returnedFrame) {
                    if (--rCtx->numFrameRequests)
                        ar = arFrameReady;
                    else
                        ar = arAllFramesReady;

                    Q_ASSERT(rCtx->numFrameRequests >= 0);
                    rCtx->availableFrames.insert(NodeOutputKey(pCtx->clip, pCtx->n, pCtx->index), pCtx->returnedFrame);
                    rCtx->lastCompletedN = pCtx->n;
                    rCtx->lastCompletedNode = pCtx->node;
                }

                owner->lock.unlock();
                // run task

                PVideoFrame f = rCtx->clip->getFrameInternal(rCtx->n, ar, rCtx);
                ranTask = true;
                owner->lock.lock();

                if (f && rCtx->numFrameRequests > 0)
                    qFatal("Frame returned but there are still pending frame requests, filter: " + rCtx->clip->name);

                owner->runningTasks.remove(FrameKey(rCtx->clip, rCtx->clip->filterMode == fmUnordered ? -1 : rCtx->n));

				if (f || ar == arError || rCtx->hasError()) {
                    // free all input frames quickly since the frame processing is done
                    rCtx->availableFrames.clear();

                    if (isSingleInstance) {
                        if (owner->framesInProgress[rCtx->clip] != rCtx->n && !rCtx->hasError())
                            qWarning("Releasing unobtained frame lock");

                        owner->framesInProgress.remove(rCtx->clip);
                    }

                    owner->allContexts.remove(NodeOutputKey(rCtx->clip, rCtx->n, rCtx->index));
                }

                if (rCtx->hasError()) {
                    PFrameContext n;

                    do {
                        n = rCtx->notificationChain;

                        if (n) {
                            rCtx->notificationChain.clear();
                            n->setError(rCtx->getErrorMessage());
                        }

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

                        if (rCtx->frameDone) {
                            owner->lock.unlock();
                            QMutexLocker callbackLock(&owner->callbackLock);
                            rCtx->frameDone(rCtx->userData, NULL, rCtx->n, rCtx->node, rCtx->getErrorMessage().constData());
                            callbackLock.unlock();
                            owner->lock.lock();
                        }
                    } while ((rCtx = n));
                } else if (f) {
                    Q_ASSERT(rCtx->numFrameRequests == 0);
                    PFrameContext n;

                    do {
                        n = rCtx->notificationChain;

                        if (n)
                            rCtx->notificationChain.clear();

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

                        if (rCtx->frameDone)
                            owner->returnFrame(rCtx, f);
                    } while ((rCtx = n));
                } else if (rCtx->numFrameRequests > 0 || rCtx->n < 0) {
                    // already scheduled or in the case of negative n it is simply a cache notify message
                } else {
                    qFatal("No frame returned at the end of processing by " + rCtx->clip->name);
                }

                break;
            }
        }

        if (!ranTask && !stop) {
            owner->activeThreads--;
            owner->newWork.wait(&owner->lock);
            owner->activeThreads++;
        } else if (stop) {
            owner->activeThreads--;
            owner->lock.unlock();
            return;
        }
    }
}

VSThreadPool::VSThreadPool(VSCore *core, int threads) : core(core), activeThreads(0) {
    setThreadCount(threads > 0 ? threads : QThread::idealThreadCount());
}

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

int	VSThreadPool::threadCount() const {
    return allThreads.count();
}

void VSThreadPool::setThreadCount(int threadCount) {
    QMutexLocker m(&lock);

    while (threadCount > allThreads.count()) {
        VSThread *vst = new VSThread(this);
        allThreads.insert(vst);
        vst->start();
    }

    while (threadCount < allThreads.count()) {
        VSThread *t = *allThreads.begin();
        t->stopThread();
        newWork.wakeAll();
        m.unlock();
        t->wait();
        m.relock();
        allThreads.remove(t);
        delete t;
        newWork.wakeAll();
    }
}

void VSThreadPool::wakeThread() {
    if (activeThreads < threadCount())
        newWork.wakeOne();
}

void VSThreadPool::releaseThread() {
    setThreadCount(allThreads.count() + 1);
}

void VSThreadPool::reserveThread() {
    setThreadCount(allThreads.count() - 1);
}

void VSThreadPool::notifyCaches(CacheActivation reason) {
    for (int i = 0; i < core->caches.count(); i++)
        tasks.insert(0, PFrameContext(new FrameContext(reason, 0, core->caches[i], PFrameContext())));
}

void VSThreadPool::start(const PFrameContext &context) {
    Q_ASSERT(context);
    QMutexLocker m(&lock);
    startInternal(context);
}

void VSThreadPool::returnFrame(const PFrameContext &rCtx, const PVideoFrame &f) {
    Q_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);
    QMutexLocker m(&callbackLock);
    rCtx->frameDone(rCtx->userData, ref, rCtx->n, rCtx->node, NULL);
    m.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)
        qFatal("Negative frame request by: " + context->clip->name);

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

    // a normal tick for caches to adjust their sizes based on recent history
    if (!context->upstreamContext && ticks.fetchAndAddAcquire(1) == 99) {
        ticks = 0;
        notifyCaches(cCacheTick);
    }

    // add it immediately if the task is to return a completed frame
    if (context->returnedFrame) {
        tasks.append(context);
        wakeThread();
        return;
    } else {
        if (context->upstreamContext)
            context->upstreamContext->numFrameRequests++;

        ////////////////////////
        // see if the task is a duplicate
        foreach(const PFrameContext &ctx, tasks) {
            if (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.append(context);
                    wakeThread();
                    return;
                } else {
                    PFrameContext rCtx = ctx;

                    if (rCtx->returnedFrame)
                        rCtx = rCtx->upstreamContext;

                    if (context->clip == rCtx->clip && context->n == rCtx->n && context->index == ctx->index) {
                        PFrameContext t = rCtx;

                        while (t && t->notificationChain)
                            t = t->notificationChain;

                        t->notificationChain = context;
                        return;
                    }
                }
            }
        }

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

        if (allContexts.contains(p)) {
            PFrameContext ctx = allContexts[p];
            Q_ASSERT(ctx);
            Q_ASSERT(context->clip == ctx->clip && context->n == ctx->n);

            if (ctx->returnedFrame) {
                // special case where the requested frame is encountered "by accident"
                context->returnedFrame = ctx->returnedFrame;
                tasks.append(context);
                wakeThread();
                return;
            } else {
                while (ctx->notificationChain)
                    ctx = ctx->notificationChain;
                ctx->notificationChain = context;
                return;
            }
        } else {
            allContexts[p] = context;
        }

        tasks.append(context);
        wakeThread();
        return;
    }

}

void VSThreadPool::waitForDone() {

}

VSThreadPool::~VSThreadPool() {
    setThreadCount(0);
};
<|endoftext|>


<commit_before>/*!
 * @file cudapolisher.cpp
 *
 * @brief CUDA Polisher class source file
 */

#include <future>
#include <iostream>
#include <chrono>
#include <cuda_profiler_api.h>

#include "sequence.hpp"
#include "cudapolisher.hpp"
#include <cudautils/cudautils.hpp>

#include "bioparser/bioparser.hpp"

namespace racon {

// The logger used by racon has a fixed size of 20 bins
// which is used for the progress bar updates. Hence all
// updates need to be broken into 20 bins.
const uint32_t RACON_LOGGER_BIN_SIZE = 20;

CUDAPolisher::CUDAPolisher(std::unique_ptr<bioparser::Parser<Sequence>> sparser,
    std::unique_ptr<bioparser::Parser<Overlap>> oparser,
    std::unique_ptr<bioparser::Parser<Sequence>> tparser,
    PolisherType type, uint32_t window_length, double quality_threshold,
    double error_threshold, int8_t match, int8_t mismatch, int8_t gap,
    uint32_t num_threads, uint32_t cuda_batches, bool cuda_banded_alignment)
        : Polisher(std::move(sparser), std::move(oparser), std::move(tparser),
                type, window_length, quality_threshold,
                error_threshold, match, mismatch, gap, num_threads)
        , cuda_batches_(cuda_batches)
        , gap_(gap)
        , mismatch_(mismatch)
        , match_(match)
        , cuda_banded_alignment_(cuda_banded_alignment)
{
#ifdef DEBUG
    window_length_ = 200;
    std::cerr << "In DEBUG mode. Using window size of " << window_length_ << std::endl;
#endif

    genomeworks::cudapoa::Init();
    genomeworks::cudaaligner::Init();

    GW_CU_CHECK_ERR(cudaGetDeviceCount(&num_devices_));

    if (num_devices_ < 1)
    {
        throw std::runtime_error("No GPU devices found.");
    }

    std::cerr << "Using " << num_devices_ << " GPU(s) to perform polishing" << std::endl;

    // Run dummy call on each device to initialize CUDA context.
    for(int32_t dev_id = 0; dev_id < num_devices_; dev_id++)
    {
        std::cerr << "Initialize device " << dev_id << std::endl;
        GW_CU_CHECK_ERR(cudaSetDevice(dev_id));
        GW_CU_CHECK_ERR(cudaFree(0));
    }

    std::cerr << "[CUDAPolisher] Constructed." << std::endl;
}

CUDAPolisher::~CUDAPolisher()
{
    cudaDeviceSynchronize();
    cudaProfilerStop();
}

void CUDAPolisher::find_overlap_breaking_points(std::vector<std::unique_ptr<Overlap>>& overlaps)
{
    std::mutex mutex_overlaps;
    uint32_t next_overlap_index = 0;

    // Lambda expression for filling up next batch of alignments.
    auto fill_next_batch = [&mutex_overlaps, &next_overlap_index, &overlaps, this](CUDABatchAligner* batch) -> void {
        batch->reset();

        // Use mutex to read the vector containing windows in a threadsafe manner.
        std::lock_guard<std::mutex> guard(mutex_overlaps);

        uint32_t initial_count = next_overlap_index;
        uint32_t count = overlaps.size();
        while(next_overlap_index < count)
        {
            if (batch->addOverlap(overlaps.at(next_overlap_index).get(), sequences_))
            {
                next_overlap_index++;
            }
            else
            {
                break;
            }
        }
        if (next_overlap_index - initial_count > 0)
        {
            fprintf(stderr, "Processing overlaps %d - %d (of %d) in batch %d\n",
                    initial_count,
                    next_overlap_index ,
                    count,
                    batch->getBatchID());
        }
    };

    // Lambda expression for processing a batch of alignments.
    auto process_batch = [&fill_next_batch, this](CUDABatchAligner* batch) -> void {
        while(true)
        {
            fill_next_batch(batch);
            if (batch->hasOverlaps())
            {
                // Launch workload.
                batch->alignAll();
                batch->find_breaking_points(window_length_);
            }
            else
            {
                break;
            }
        }
    };

    // Create batches based on arguments provided to program.
    for(uint32_t batch = 0; batch < cuda_batches_; batch++)
    {
        batch_aligners_.emplace_back(createCUDABatchAligner(20000, 20000, 1000, 0));
    }

    // Run batched alignment.
    std::vector<std::future<void>> thread_futures;
    for(uint32_t i = 0; i < batch_aligners_.size(); i++)
    {
        thread_futures.emplace_back(std::async(std::launch::async,
                                               process_batch,
                                               batch_aligners_.at(i).get())
                                   );
    }

    // Wait for threads to finish, and collect their results.
    for (const auto& future : thread_futures) {
        future.wait();
    }

    batch_aligners_.clear();

    log(std::string("[racon::CUDAPolisher::initialize] aligned overlaps"));

    // TODO: Kept CPU overlap alignment right now while GPU is a dummy implmentation.
    Polisher::find_overlap_breaking_points(overlaps);
}

void CUDAPolisher::polish(std::vector<std::unique_ptr<Sequence>>& dst,
    bool drop_unpolished_sequences)
{
    // Creation and use of batches.
    const uint32_t MAX_WINDOWS = 256;
    const uint32_t MAX_DEPTH_PER_WINDOW = 200;

    // Bin batches into each GPU.
    std::vector<uint32_t> batches_per_gpu(num_devices_, 0);

#ifdef DEBUG
    for(uint32_t i = 0; i < 1; i++)
#else
    for(uint32_t i = 0; i < cuda_batches_; i++)
#endif
    {
        uint32_t device = i % num_devices_;
        batches_per_gpu.at(device) = batches_per_gpu.at(device) + 1;
    }

    for(int32_t device = 0; device < num_devices_; device++)
    {
        for(uint32_t batch = 0; batch < batches_per_gpu.at(device); batch++)
        {
            batch_processors_.emplace_back(createCUDABatch(MAX_WINDOWS, MAX_DEPTH_PER_WINDOW, device, gap_, mismatch_, match_, cuda_banded_alignment_));
        }
    }

    log(std::string("[racon::CUDAPolisher::polish] allocated memory on GPUs"));

    // Mutex for accessing the vector of windows.
    std::mutex mutex_windows;

    // Initialize window consensus statuses.
    window_consensus_status_.resize(windows_.size(), false);

    // Index of next window to be added to a batch.
#ifdef DEBUG
    uint32_t next_window_index = 5000;
#else
    uint32_t next_window_index = 0;
#endif

    // Variables for keeping track of logger progress bar.
    uint32_t logger_step = windows_.size() / RACON_LOGGER_BIN_SIZE;
    uint32_t last_logger_count = 0;

    // Lambda function for adding windows to batches.
    auto fill_next_batch = [&mutex_windows, &next_window_index, &logger_step, &last_logger_count, this](CUDABatchProcessor* batch) -> std::pair<uint32_t, uint32_t> {
        batch->reset();

        // Use mutex to read the vector containing windows in a threadsafe manner.
        std::lock_guard<std::mutex> guard(mutex_windows);

        // TODO: Reducing window wize by 10 for debugging.
        uint32_t initial_count = next_window_index;
#ifdef DEBUG
        uint32_t count = 5001;//windows_.size();
#else
        uint32_t count = windows_.size();
#endif
        while(next_window_index < count)
        {
            if (batch->addWindow(windows_.at(next_window_index)))
            {
                next_window_index++;
            }
            else
            {
                break;
            }
        }

        uint32_t logger_count = initial_count / logger_step;
        if (next_window_index - initial_count > 0 && logger_count > last_logger_count)
        {
            bar(std::string("[racon::CUDAPolisher::polish] generating consensus"));
            last_logger_count++;
        }

        return std::pair<uint32_t, uint32_t>(initial_count, next_window_index);
    };

    // Lambda function for processing each batch.
    auto process_batch = [&fill_next_batch, this](CUDABatchProcessor* batch) -> void {
        while(true)
        {
            std::pair<uint32_t, uint32_t> range = fill_next_batch(batch);
            if (batch->hasWindows())
            {
                // Launch workload.
                const std::vector<bool>& results = batch->generateConsensus();

                // Check if the number of batches processed is same as the range of
                // of windows that were added.
                if (results.size() != (range.second - range.first))
                {
                    throw std::runtime_error("Windows processed doesn't match \
                            range of windows passed to batch\n");
                }

                // Copy over the results from the batch into the per window
                // result vector of the CUDAPolisher.
                for(uint32_t i = 0; i < results.size(); i++)
                {
                    window_consensus_status_.at(range.first + i) = results.at(i);
                }
            }
            else
            {
                break;
            }
        }
    };

    // Process each of the batches in a separate thread.
    std::vector<std::future<void>> thread_futures;
    for(uint32_t i = 0; i < batch_processors_.size(); i++)
    {
        thread_futures.emplace_back(std::async(std::launch::async,
                                               process_batch,
                                               batch_processors_.at(i).get())
                                   );
    }

    // Wait for threads to finish, and collect their results.
    for (const auto& future : thread_futures) {
        future.wait();
    }

    // Process each failed windows in parallel on CPU
    std::vector<std::future<bool>> thread_failed_windows;
    for (uint64_t i = 0; i < windows_.size(); ++i) {
        if (window_consensus_status_.at(i) == false){
            thread_failed_windows.emplace_back(thread_pool_->submit_task(
                [&](uint64_t j) -> bool {
                    auto it = thread_to_id_.find(std::this_thread::get_id());
                    if (it == thread_to_id_.end()) {
                        fprintf(stderr, "[racon::Polisher::polish] error: "
                            "thread identifier not present!\n");
                        exit(1);
                    }
                    return window_consensus_status_.at(j) = windows_[j]->generate_consensus(
                        alignment_engines_[it->second]);
                }, i));
        }
    }
    // Wait for threads to finish, and collect their results.
    for (const auto& t : thread_failed_windows) {
        t.wait();
    }

    if (logger_step != 0) {
        bar(std::string("[racon::CUDAPolisher::polish] generating consensus"));
    } else {
        log(std::string("[racon::CUDAPolisher::polish] generating consensus"));
    }

    // Collect results from all windows into final output.
    std::string polished_data = "";
    uint32_t num_polished_windows = 0;

    for (uint64_t i = 0; i < windows_.size(); ++i) {

        num_polished_windows += window_consensus_status_.at(i) == true ? 1 : 0;
        polished_data += windows_[i]->consensus();

        if (i == windows_.size() - 1 || windows_[i + 1]->rank() == 0) {
            double polished_ratio = num_polished_windows /
                static_cast<double>(windows_[i]->rank() + 1);

            if (!drop_unpolished_sequences || polished_ratio > 0) {
                std::string tags = type_ == PolisherType::kF ? "r" : "";
                tags += " LN:i:" + std::to_string(polished_data.size());
                tags += " RC:i:" + std::to_string(targets_coverages_[windows_[i]->id()]);
                tags += " XC:f:" + std::to_string(polished_ratio);
                dst.emplace_back(createSequence(sequences_[windows_[i]->id()]->name() +
                    tags, polished_data));
            }

            num_polished_windows = 0;
            polished_data.clear();
        }
        windows_[i].reset();
    }

    // Clear POA processors.
    batch_processors_.clear();
}

}
<commit_msg>[cudaposlisher] disable cuda aligner path<commit_after>/*!
 * @file cudapolisher.cpp
 *
 * @brief CUDA Polisher class source file
 */

#include <future>
#include <iostream>
#include <chrono>
#include <cuda_profiler_api.h>

#include "sequence.hpp"
#include "cudapolisher.hpp"
#include <cudautils/cudautils.hpp>

#include "bioparser/bioparser.hpp"

namespace racon {

// The logger used by racon has a fixed size of 20 bins
// which is used for the progress bar updates. Hence all
// updates need to be broken into 20 bins.
const uint32_t RACON_LOGGER_BIN_SIZE = 20;

CUDAPolisher::CUDAPolisher(std::unique_ptr<bioparser::Parser<Sequence>> sparser,
    std::unique_ptr<bioparser::Parser<Overlap>> oparser,
    std::unique_ptr<bioparser::Parser<Sequence>> tparser,
    PolisherType type, uint32_t window_length, double quality_threshold,
    double error_threshold, int8_t match, int8_t mismatch, int8_t gap,
    uint32_t num_threads, uint32_t cuda_batches, bool cuda_banded_alignment)
        : Polisher(std::move(sparser), std::move(oparser), std::move(tparser),
                type, window_length, quality_threshold,
                error_threshold, match, mismatch, gap, num_threads)
        , cuda_batches_(cuda_batches)
        , gap_(gap)
        , mismatch_(mismatch)
        , match_(match)
        , cuda_banded_alignment_(cuda_banded_alignment)
{
#ifdef DEBUG
    window_length_ = 200;
    std::cerr << "In DEBUG mode. Using window size of " << window_length_ << std::endl;
#endif

    genomeworks::cudapoa::Init();
    genomeworks::cudaaligner::Init();

    GW_CU_CHECK_ERR(cudaGetDeviceCount(&num_devices_));

    if (num_devices_ < 1)
    {
        throw std::runtime_error("No GPU devices found.");
    }

    std::cerr << "Using " << num_devices_ << " GPU(s) to perform polishing" << std::endl;

    // Run dummy call on each device to initialize CUDA context.
    for(int32_t dev_id = 0; dev_id < num_devices_; dev_id++)
    {
        std::cerr << "Initialize device " << dev_id << std::endl;
        GW_CU_CHECK_ERR(cudaSetDevice(dev_id));
        GW_CU_CHECK_ERR(cudaFree(0));
    }

    std::cerr << "[CUDAPolisher] Constructed." << std::endl;
}

CUDAPolisher::~CUDAPolisher()
{
    cudaDeviceSynchronize();
    cudaProfilerStop();
}

void CUDAPolisher::find_overlap_breaking_points(std::vector<std::unique_ptr<Overlap>>& overlaps)
{
#if 0
    std::mutex mutex_overlaps;
    uint32_t next_overlap_index = 0;

    // Lambda expression for filling up next batch of alignments.
    auto fill_next_batch = [&mutex_overlaps, &next_overlap_index, &overlaps, this](CUDABatchAligner* batch) -> void {
        batch->reset();

        // Use mutex to read the vector containing windows in a threadsafe manner.
        std::lock_guard<std::mutex> guard(mutex_overlaps);

        uint32_t initial_count = next_overlap_index;
        uint32_t count = overlaps.size();
        while(next_overlap_index < count)
        {
            if (batch->addOverlap(overlaps.at(next_overlap_index).get(), sequences_))
            {
                next_overlap_index++;
            }
            else
            {
                break;
            }
        }
        if (next_overlap_index - initial_count > 0)
        {
            fprintf(stderr, "Processing overlaps %d - %d (of %d) in batch %d\n",
                    initial_count,
                    next_overlap_index ,
                    count,
                    batch->getBatchID());
        }
    };

    // Lambda expression for processing a batch of alignments.
    auto process_batch = [&fill_next_batch, this](CUDABatchAligner* batch) -> void {
        while(true)
        {
            fill_next_batch(batch);
            if (batch->hasOverlaps())
            {
                // Launch workload.
                batch->alignAll();
                batch->find_breaking_points(window_length_);
            }
            else
            {
                break;
            }
        }
    };

    // Create batches based on arguments provided to program.
    for(uint32_t batch = 0; batch < cuda_batches_; batch++)
    {
        batch_aligners_.emplace_back(createCUDABatchAligner(20000, 20000, 1000, 0));
    }

    // Run batched alignment.
    std::vector<std::future<void>> thread_futures;
    for(uint32_t i = 0; i < batch_aligners_.size(); i++)
    {
        thread_futures.emplace_back(std::async(std::launch::async,
                                               process_batch,
                                               batch_aligners_.at(i).get())
                                   );
    }

    // Wait for threads to finish, and collect their results.
    for (const auto& future : thread_futures) {
        future.wait();
    }

    batch_aligners_.clear();

    log(std::string("[racon::CUDAPolisher::initialize] aligned overlaps"));
#endif

    // TODO: Kept CPU overlap alignment right now while GPU is a dummy implmentation.
    Polisher::find_overlap_breaking_points(overlaps);
}

void CUDAPolisher::polish(std::vector<std::unique_ptr<Sequence>>& dst,
    bool drop_unpolished_sequences)
{
    // Creation and use of batches.
    const uint32_t MAX_WINDOWS = 256;
    const uint32_t MAX_DEPTH_PER_WINDOW = 200;

    // Bin batches into each GPU.
    std::vector<uint32_t> batches_per_gpu(num_devices_, 0);

#ifdef DEBUG
    for(uint32_t i = 0; i < 1; i++)
#else
    for(uint32_t i = 0; i < cuda_batches_; i++)
#endif
    {
        uint32_t device = i % num_devices_;
        batches_per_gpu.at(device) = batches_per_gpu.at(device) + 1;
    }

    for(int32_t device = 0; device < num_devices_; device++)
    {
        for(uint32_t batch = 0; batch < batches_per_gpu.at(device); batch++)
        {
            batch_processors_.emplace_back(createCUDABatch(MAX_WINDOWS, MAX_DEPTH_PER_WINDOW, device, gap_, mismatch_, match_, cuda_banded_alignment_));
        }
    }

    log(std::string("[racon::CUDAPolisher::polish] allocated memory on GPUs"));

    // Mutex for accessing the vector of windows.
    std::mutex mutex_windows;

    // Initialize window consensus statuses.
    window_consensus_status_.resize(windows_.size(), false);

    // Index of next window to be added to a batch.
#ifdef DEBUG
    uint32_t next_window_index = 5000;
#else
    uint32_t next_window_index = 0;
#endif

    // Variables for keeping track of logger progress bar.
    uint32_t logger_step = windows_.size() / RACON_LOGGER_BIN_SIZE;
    uint32_t last_logger_count = 0;

    // Lambda function for adding windows to batches.
    auto fill_next_batch = [&mutex_windows, &next_window_index, &logger_step, &last_logger_count, this](CUDABatchProcessor* batch) -> std::pair<uint32_t, uint32_t> {
        batch->reset();

        // Use mutex to read the vector containing windows in a threadsafe manner.
        std::lock_guard<std::mutex> guard(mutex_windows);

        // TODO: Reducing window wize by 10 for debugging.
        uint32_t initial_count = next_window_index;
#ifdef DEBUG
        uint32_t count = 5001;//windows_.size();
#else
        uint32_t count = windows_.size();
#endif
        while(next_window_index < count)
        {
            if (batch->addWindow(windows_.at(next_window_index)))
            {
                next_window_index++;
            }
            else
            {
                break;
            }
        }

        uint32_t logger_count = initial_count / logger_step;
        if (next_window_index - initial_count > 0 && logger_count > last_logger_count)
        {
            bar(std::string("[racon::CUDAPolisher::polish] generating consensus"));
            last_logger_count++;
        }

        return std::pair<uint32_t, uint32_t>(initial_count, next_window_index);
    };

    // Lambda function for processing each batch.
    auto process_batch = [&fill_next_batch, this](CUDABatchProcessor* batch) -> void {
        while(true)
        {
            std::pair<uint32_t, uint32_t> range = fill_next_batch(batch);
            if (batch->hasWindows())
            {
                // Launch workload.
                const std::vector<bool>& results = batch->generateConsensus();

                // Check if the number of batches processed is same as the range of
                // of windows that were added.
                if (results.size() != (range.second - range.first))
                {
                    throw std::runtime_error("Windows processed doesn't match \
                            range of windows passed to batch\n");
                }

                // Copy over the results from the batch into the per window
                // result vector of the CUDAPolisher.
                for(uint32_t i = 0; i < results.size(); i++)
                {
                    window_consensus_status_.at(range.first + i) = results.at(i);
                }
            }
            else
            {
                break;
            }
        }
    };

    // Process each of the batches in a separate thread.
    std::vector<std::future<void>> thread_futures;
    for(uint32_t i = 0; i < batch_processors_.size(); i++)
    {
        thread_futures.emplace_back(std::async(std::launch::async,
                                               process_batch,
                                               batch_processors_.at(i).get())
                                   );
    }

    // Wait for threads to finish, and collect their results.
    for (const auto& future : thread_futures) {
        future.wait();
    }

    // Process each failed windows in parallel on CPU
    std::vector<std::future<bool>> thread_failed_windows;
    for (uint64_t i = 0; i < windows_.size(); ++i) {
        if (window_consensus_status_.at(i) == false){
            thread_failed_windows.emplace_back(thread_pool_->submit_task(
                [&](uint64_t j) -> bool {
                    auto it = thread_to_id_.find(std::this_thread::get_id());
                    if (it == thread_to_id_.end()) {
                        fprintf(stderr, "[racon::Polisher::polish] error: "
                            "thread identifier not present!\n");
                        exit(1);
                    }
                    return window_consensus_status_.at(j) = windows_[j]->generate_consensus(
                        alignment_engines_[it->second]);
                }, i));
        }
    }
    // Wait for threads to finish, and collect their results.
    for (const auto& t : thread_failed_windows) {
        t.wait();
    }

    if (logger_step != 0) {
        bar(std::string("[racon::CUDAPolisher::polish] generating consensus"));
    } else {
        log(std::string("[racon::CUDAPolisher::polish] generating consensus"));
    }

    // Collect results from all windows into final output.
    std::string polished_data = "";
    uint32_t num_polished_windows = 0;

    for (uint64_t i = 0; i < windows_.size(); ++i) {

        num_polished_windows += window_consensus_status_.at(i) == true ? 1 : 0;
        polished_data += windows_[i]->consensus();

        if (i == windows_.size() - 1 || windows_[i + 1]->rank() == 0) {
            double polished_ratio = num_polished_windows /
                static_cast<double>(windows_[i]->rank() + 1);

            if (!drop_unpolished_sequences || polished_ratio > 0) {
                std::string tags = type_ == PolisherType::kF ? "r" : "";
                tags += " LN:i:" + std::to_string(polished_data.size());
                tags += " RC:i:" + std::to_string(targets_coverages_[windows_[i]->id()]);
                tags += " XC:f:" + std::to_string(polished_ratio);
                dst.emplace_back(createSequence(sequences_[windows_[i]->id()]->name() +
                    tags, polished_data));
            }

            num_polished_windows = 0;
            polished_data.clear();
        }
        windows_[i].reset();
    }

    // Clear POA processors.
    batch_processors_.clear();
}

}
<|endoftext|>


<commit_before>
#include "MenuItem.hpp"
#include "Menu.hpp"
#include "Message.hpp"
#include "Window.hpp"
#include "registory.hpp"
#include "convert.hpp"
#include "deleting.hpp"
#include "ownership.hpp"

namespace rbe
{
	namespace gc
	{
		template<>
		void Deleting<BMenuItem, BMenu>(BMenuItem *o, BMenu *t)
		{
			if (o->fSubmenu == t)
				o->fSubmenu = NULL;
		}
	}

	namespace B
	{
		void
		MenuItem::rbe__gc_mark(void *ptr)
		{
			BMenuItem *_this = static_cast<BMenuItem *>(ptr);
			if (_this->fWindow != NULL) {
				VALUE vwindow = Convert<BWindow *>::ToValue(_this->fWindow);
				rb_gc_mark(vwindow);
			}
			ObjectRegistory::Instance()->Mark(ptr);
		}

		void
		MenuItem::rbe__gc_free(void *ptr)
		{
			BMenuItem *_this = static_cast<BMenuItem *>(ptr);
			ObjectRegistory::Instance()->Delete(ptr);
			_this->fMessage = NULL;
			_this->fSubmenu = NULL;
			delete _this;
		}

		VALUE MenuItem::rbe__initialize(int argc, VALUE *argv, VALUE self)
		{
			VALUE vargs[4];
			BMenuItem *_this = NULL;

			rb_scan_args(argc, argv, "13", &vargs[0], &vargs[1], &vargs[2], &vargs[3]);

			if (2 <= argc && argc <= 4) {
				if (!Convert<const char *>::IsConvertable(vargs[0])) goto break_0;
				if (!Convert<BMessage *>::IsConvertable(vargs[1])) goto break_0;
				if (2 < argc && !Convert<char>::IsConvertable(vargs[2])) goto break_0;
				if (3 < argc && !Convert<uint32>::IsConvertable(vargs[3])) goto break_0;
				const char * label = Convert<const char *>::FromValue(vargs[0]);
				BMessage * message = Convert<BMessage *>::FromValue(vargs[1]);
				char shortcut = (2 < argc ? Convert<char>::FromValue(vargs[2]) : 0);
				uint32 modifiers = (3 < argc ? Convert<uint32>::FromValue(vargs[3]) : 0);
				_this = new BMenuItem(label, message, shortcut, modifiers);
				PointerOf<BMenuItem>::Class *ptr = static_cast<PointerOf<BMenuItem>::Class *>(_this);
				DATA_PTR(self) = static_cast<void *>(ptr);
				ObjectRegistory::Instance()->Add(self);
				if (!NIL_P(argv[1])) {
					gc::Ownership0 *ownership = new gc::Ownership<BInvoker, BMessage>(argv[1]);
					ObjectRegistory::Instance()->Own(self, ownership);
				}
				goto fin;
			}
		break_0:

			if (1 <= argc && argc <= 2) {
				if (!Convert<BMenu *>::IsConvertable(vargs[0])) goto break_1;
				if (1 < argc && !Convert<BMessage *>::IsConvertable(vargs[1])) goto break_1;
				BMenu * menu = Convert<BMenu *>::FromValue(vargs[0]);
				BMessage * message = (1 < argc ? Convert<BMessage *>::FromValue(vargs[1]) : NULL);
				_this = new BMenuItem(menu, message);
				PointerOf<BMenuItem>::Class *ptr = static_cast<PointerOf<BMenuItem>::Class *>(_this);
				DATA_PTR(self) = static_cast<void *>(ptr);
				ObjectRegistory::Instance()->Add(self);
				if (!NIL_P(argv[0])) {
					gc::Ownership0 *ownership = new gc::Ownership<BMenuItem, BMenu>(argv[0]);
					ObjectRegistory::Instance()->Own(self, ownership);
				}
				if (!NIL_P(argv[1])) {
					gc::Ownership0 *ownership = new gc::Ownership<BInvoker, BMessage>(argv[1]);
					ObjectRegistory::Instance()->Own(self, ownership);
				}
				goto fin;
			}
		break_1:

		fin:
			if (_this == NULL)
				rb_raise(rb_eTypeError, "wrong type of arguments");
			return self;
		}
	}
}
<commit_msg>B::MenuItem marks it's menu (fSuper)<commit_after>
#include "MenuItem.hpp"
#include "Menu.hpp"
#include "Message.hpp"
#include "Window.hpp"
#include "registory.hpp"
#include "convert.hpp"
#include "deleting.hpp"
#include "ownership.hpp"

namespace rbe
{
	namespace gc
	{
		template<>
		void Deleting<BMenuItem, BMenu>(BMenuItem *o, BMenu *t)
		{
			if (o->fSubmenu == t)
				o->fSubmenu = NULL;
		}
	}

	namespace B
	{
		void
		MenuItem::rbe__gc_mark(void *ptr)
		{
			BMenuItem *_this = static_cast<BMenuItem *>(ptr);
			if (_this->fWindow != NULL) {
				VALUE vwindow = Convert<BWindow *>::ToValue(_this->fWindow);
				rb_gc_mark(vwindow);
			}
			if (_this->fSuper != NULL) {
				VALUE vmenu = Convert<BMenu *>::ToValue(_this->fSuper);
				rb_gc_mark(vmenu);
			}
			ObjectRegistory::Instance()->Mark(ptr);
		}

		void
		MenuItem::rbe__gc_free(void *ptr)
		{
			BMenuItem *_this = static_cast<BMenuItem *>(ptr);
			ObjectRegistory::Instance()->Delete(ptr);
			_this->fMessage = NULL;
			_this->fSubmenu = NULL;
			delete _this;
		}

		VALUE MenuItem::rbe__initialize(int argc, VALUE *argv, VALUE self)
		{
			VALUE vargs[4];
			BMenuItem *_this = NULL;

			rb_scan_args(argc, argv, "13", &vargs[0], &vargs[1], &vargs[2], &vargs[3]);

			if (2 <= argc && argc <= 4) {
				if (!Convert<const char *>::IsConvertable(vargs[0])) goto break_0;
				if (!Convert<BMessage *>::IsConvertable(vargs[1])) goto break_0;
				if (2 < argc && !Convert<char>::IsConvertable(vargs[2])) goto break_0;
				if (3 < argc && !Convert<uint32>::IsConvertable(vargs[3])) goto break_0;
				const char * label = Convert<const char *>::FromValue(vargs[0]);
				BMessage * message = Convert<BMessage *>::FromValue(vargs[1]);
				char shortcut = (2 < argc ? Convert<char>::FromValue(vargs[2]) : 0);
				uint32 modifiers = (3 < argc ? Convert<uint32>::FromValue(vargs[3]) : 0);
				_this = new BMenuItem(label, message, shortcut, modifiers);
				PointerOf<BMenuItem>::Class *ptr = static_cast<PointerOf<BMenuItem>::Class *>(_this);
				DATA_PTR(self) = static_cast<void *>(ptr);
				ObjectRegistory::Instance()->Add(self);
				if (!NIL_P(argv[1])) {
					gc::Ownership0 *ownership = new gc::Ownership<BInvoker, BMessage>(argv[1]);
					ObjectRegistory::Instance()->Own(self, ownership);
				}
				goto fin;
			}
		break_0:

			if (1 <= argc && argc <= 2) {
				if (!Convert<BMenu *>::IsConvertable(vargs[0])) goto break_1;
				if (1 < argc && !Convert<BMessage *>::IsConvertable(vargs[1])) goto break_1;
				BMenu * menu = Convert<BMenu *>::FromValue(vargs[0]);
				BMessage * message = (1 < argc ? Convert<BMessage *>::FromValue(vargs[1]) : NULL);
				_this = new BMenuItem(menu, message);
				PointerOf<BMenuItem>::Class *ptr = static_cast<PointerOf<BMenuItem>::Class *>(_this);
				DATA_PTR(self) = static_cast<void *>(ptr);
				ObjectRegistory::Instance()->Add(self);
				if (!NIL_P(argv[0])) {
					gc::Ownership0 *ownership = new gc::Ownership<BMenuItem, BMenu>(argv[0]);
					ObjectRegistory::Instance()->Own(self, ownership);
				}
				if (!NIL_P(argv[1])) {
					gc::Ownership0 *ownership = new gc::Ownership<BInvoker, BMessage>(argv[1]);
					ObjectRegistory::Instance()->Own(self, ownership);
				}
				goto fin;
			}
		break_1:

		fin:
			if (_this == NULL)
				rb_raise(rb_eTypeError, "wrong type of arguments");
			return self;
		}
	}
}
<|endoftext|>