<commit_before>//
//  libavg - Media Playback Engine.
//  Copyright (C) 2003-2008 Ulrich von Zadow
//
//  This library 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 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//  Current versions can be found at www.libavg.de
//

#include "AsyncVideoDecoder.h"
#include "EOFVideoMsg.h"
#include "ErrorVideoMsg.h"
#include "SeekDoneVideoMsg.h"

#include "../base/ObjectCounter.h"

#include <boost/thread/thread.hpp>
#include <boost/bind.hpp>

#include <math.h>
#include <iostream>

using namespace boost;
using namespace std;

namespace avg {

AsyncVideoDecoder::AsyncVideoDecoder(VideoDecoderPtr pSyncDecoder)
    : m_pSyncDecoder(pSyncDecoder),
      m_pVDecoderThread(0),
      m_pADecoderThread(0),
      m_Size(0,0),
      m_NumFrames(0),
      m_FPS(0),
      m_PF(NO_PIXELFORMAT),
      m_Duration(0),
      m_bAudioEOF(false),
      m_bVideoEOF(false),
      m_bSeekPending(false),
      m_Volume(1.0),
      m_LastVideoFrameTime(-1000),
      m_LastAudioFrameTime(-1000)
{
    ObjectCounter::get()->incRef(&typeid(*this));
}

AsyncVideoDecoder::~AsyncVideoDecoder()
{
    if (m_pVDecoderThread || m_pADecoderThread) {
        close();
    }
    ObjectCounter::get()->decRef(&typeid(*this));
}

void AsyncVideoDecoder::open(const std::string& sFilename, const AudioParams* pAP,
        YCbCrMode ycbcrMode, bool bThreadedDemuxer)
{
    m_bAudioEOF = false;
    m_bVideoEOF = false;
    m_bSeekPending = false;
    m_sFilename = sFilename;

    m_pSyncDecoder->open(m_sFilename, pAP, ycbcrMode, bThreadedDemuxer);
    m_bHasVideo = m_pSyncDecoder->hasVideo();
    m_bHasAudio = m_pSyncDecoder->hasAudio();
    m_Duration = m_pSyncDecoder->getDuration();

    if (m_bHasVideo) {
        m_LastVideoFrameTime = -1000;
        m_Size = m_pSyncDecoder->getSize();
        m_NumFrames = m_pSyncDecoder->getNumFrames();
        m_FPS = m_pSyncDecoder->getFPS();
        m_PF = m_pSyncDecoder->getPixelFormat();
        m_StreamFPS = m_pSyncDecoder->getNominalFPS();
        m_pVCmdQ = VideoDecoderThread::CmdQueuePtr(new VideoDecoderThread::CmdQueue);
        m_pVMsgQ = VideoMsgQueuePtr(new VideoMsgQueue(8));
        m_pVDecoderThread = new boost::thread(
                 VideoDecoderThread(*m_pVCmdQ, *m_pVMsgQ, m_pSyncDecoder));
    }

    if (m_bHasAudio) {
        m_pACmdQ = AudioDecoderThread::CmdQueuePtr(new AudioDecoderThread::CmdQueue);
        m_pAMsgQ = VideoMsgQueuePtr(new VideoMsgQueue(8));
        m_pADecoderThread = new boost::thread(
                 AudioDecoderThread(*m_pACmdQ, *m_pAMsgQ, m_pSyncDecoder, *pAP));
        m_AudioMsgData = 0;
        m_AudioMsgSize = 0;
        m_LastAudioFrameTime = 0;
    }
}

void AsyncVideoDecoder::close()
{
    if (m_pVDecoderThread) {
        m_pVCmdQ->push(Command<VideoDecoderThread>(boost::bind(
                &VideoDecoderThread::stop, _1)));
        getNextBmps(false); // If the Queue is full, this breaks the lock in the thread.
        m_pVDecoderThread->join();
        delete m_pVDecoderThread;
        m_pVDecoderThread = 0;
    }
    {
        scoped_lock Lock1(m_AudioMutex);
        if (m_pADecoderThread) {
            m_pACmdQ->push(Command<AudioDecoderThread>(boost::bind(
                    &AudioDecoderThread::stop, _1)));
            try {
                m_pAMsgQ->pop(false);
            } catch(Exception&) {}
            m_pADecoderThread->join();
            delete m_pADecoderThread;
            m_pADecoderThread = 0;
        }
        m_pSyncDecoder->close();
    }
}

void AsyncVideoDecoder::seek(long long DestTime)
{
    waitForSeekDone();
    scoped_lock Lock1(m_AudioMutex);
    scoped_lock Lock2(m_SeekMutex);
    m_bAudioEOF = false;
    m_bVideoEOF = false;
    m_bSeekPending = false;
    m_LastVideoFrameTime = -1000;
    m_bSeekPending = true;
    if (m_pVCmdQ) {
        m_pVCmdQ->push(Command<VideoDecoderThread>(boost::bind(
                    &VideoDecoderThread::seek, _1, DestTime)));
    } else {
        m_pACmdQ->push(Command<AudioDecoderThread>(boost::bind(
                    &AudioDecoderThread::seek, _1, DestTime)));
    }
    try {
        while (m_bSeekPending) {
            VideoMsgPtr pMsg;
            if (m_pVCmdQ) {
                pMsg = m_pVMsgQ->pop(false);
            } else {
                pMsg = m_pAMsgQ->pop(false);
            }
            SeekDoneVideoMsgPtr pSeekDoneMsg = dynamic_pointer_cast<SeekDoneVideoMsg>(pMsg);
            if (pSeekDoneMsg) {
                m_bSeekPending = false;
                m_LastVideoFrameTime = pSeekDoneMsg->getVideoFrameTime();
                m_LastAudioFrameTime = pSeekDoneMsg->getAudioFrameTime();
            }
        }
    } catch (Exception&) {
    }
}

StreamSelect AsyncVideoDecoder::getMasterStream()
{
    if (m_bHasAudio) {
        return SS_AUDIO;
    } else {
        return SS_VIDEO;
    }
}

bool AsyncVideoDecoder::hasVideo()
{
    return m_bHasVideo;
}

bool AsyncVideoDecoder::hasAudio()
{
    return m_bHasAudio;
}

IntPoint AsyncVideoDecoder::getSize()
{
    return m_Size;
}

int AsyncVideoDecoder::getCurFrame()
{
    return int(getCurTime(SS_VIDEO)*m_StreamFPS/1000.0+0.5);
}

int AsyncVideoDecoder::getNumFrames()
{
    if (m_NumFrames == 0) {
        throw Exception(AVG_ERR_VIDEO_GENERAL, "Error in AsyncVideoDecoder::getNumFrames: Video not loaded.");
    }
    return m_NumFrames;
}

int AsyncVideoDecoder::getNumFramesQueued()
{
    return m_pVMsgQ->size();
}

long long AsyncVideoDecoder::getCurTime(StreamSelect Stream)
{
    switch(Stream) {
        case SS_VIDEO:
            assert(m_bHasVideo);
            return m_LastVideoFrameTime;
            break;
        case SS_AUDIO:
            assert(m_bHasAudio);
            return m_LastAudioFrameTime;
            break;
        case SS_DEFAULT:
            return getCurTime(getMasterStream());
            break;
        default:
            assert(false);
    }
    return -1;
}

long long AsyncVideoDecoder::getDuration()
{
    return m_Duration;
}

double AsyncVideoDecoder::getNominalFPS()
{
    return m_StreamFPS;
}

double AsyncVideoDecoder::getFPS()
{
    assert(m_pVDecoderThread);
    return m_FPS;
}

void AsyncVideoDecoder::setFPS(double FPS)
{
    assert(!m_pADecoderThread);
    m_pVCmdQ->push(Command<VideoDecoderThread>(boost::bind(
            &VideoDecoderThread::setFPS, _1, FPS)));
    if (FPS != 0) {
        m_FPS = FPS;
    }
}

double AsyncVideoDecoder::getVolume()
{
    return m_Volume;
}

void AsyncVideoDecoder::setVolume(double Volume)
{
    m_Volume = Volume;
    if (m_bHasAudio) {
        m_pACmdQ->push(Command<AudioDecoderThread>(boost::bind(
                &AudioDecoderThread::setVolume, _1, Volume)));
    }
}

PixelFormat AsyncVideoDecoder::getPixelFormat()
{
    assert(m_pVDecoderThread);
    return m_PF;
}

FrameAvailableCode AsyncVideoDecoder::renderToBmp(BitmapPtr pBmp, long long TimeWanted)
{
    FrameAvailableCode FrameAvailable;
    FrameVideoMsgPtr pFrameMsg = getBmpsForTime(TimeWanted, FrameAvailable);
    if (FrameAvailable == FA_NEW_FRAME) {
        pBmp->copyPixels(*(pFrameMsg->getBitmap(0)));
    }
    return FrameAvailable;
}

FrameAvailableCode AsyncVideoDecoder::renderToYCbCr420p(BitmapPtr pBmpY, BitmapPtr pBmpCb,
       BitmapPtr pBmpCr, long long TimeWanted)
{
    FrameAvailableCode FrameAvailable;
    FrameVideoMsgPtr pFrameMsg = getBmpsForTime(TimeWanted, FrameAvailable);
    if (FrameAvailable == FA_NEW_FRAME) {
        pBmpY->copyPixels(*(pFrameMsg->getBitmap(0)));
        pBmpCb->copyPixels(*(pFrameMsg->getBitmap(1)));
        pBmpCr->copyPixels(*(pFrameMsg->getBitmap(2)));
    }
    return FrameAvailable;
}

bool AsyncVideoDecoder::isEOF(StreamSelect Stream)
{
    switch(Stream) {
        case SS_AUDIO:
            return (!m_bHasAudio || m_bAudioEOF);
        case SS_VIDEO:
            return (!m_bHasVideo || m_bVideoEOF);
        case SS_ALL:
            return isEOF(SS_VIDEO) && isEOF(SS_AUDIO);
        default:
            return false;
    }
}

int AsyncVideoDecoder::fillAudioBuffer(AudioBufferPtr pBuffer)
{
    assert (m_pADecoderThread);
    if (m_bAudioEOF) {
        return 0;
    }
    scoped_lock Lock(m_AudioMutex);
    waitForSeekDone();

    unsigned char* audioBuffer = (unsigned char *)(pBuffer->getData());
    int audioBufferSize = pBuffer->getNumBytes();

    int bufferLeftToFill = audioBufferSize;
    while (bufferLeftToFill > 0) {
        while (m_AudioMsgSize > 0 && bufferLeftToFill > 0) {
            int copyBytes = min(bufferLeftToFill, m_AudioMsgSize);
            memcpy(audioBuffer, m_AudioMsgData, copyBytes);
            m_AudioMsgSize -= copyBytes;
            m_AudioMsgData += copyBytes;
            bufferLeftToFill -= copyBytes;
            audioBuffer += copyBytes;

            m_LastAudioFrameTime += (long long)(1000.0 * copyBytes /
                    (pBuffer->getFrameSize() * pBuffer->getRate()));
        }
        if (bufferLeftToFill != 0) {
            try {
                VideoMsgPtr pMsg = m_pAMsgQ->pop(false);

                EOFVideoMsgPtr pEOFMsg(dynamic_pointer_cast<EOFVideoMsg>(pMsg));
                if (pEOFMsg) {
                    m_bAudioEOF = true;
                    return pBuffer->getNumFrames()-bufferLeftToFill/pBuffer->getFrameSize();
                }

                m_pAudioMsg = dynamic_pointer_cast<AudioVideoMsg>(pMsg);
                assert(m_pAudioMsg);

                m_AudioMsgSize = m_pAudioMsg->getBuffer()->getNumFrames()
                        *pBuffer->getFrameSize();
                m_AudioMsgData = (unsigned char *)(m_pAudioMsg->getBuffer()->getData());
                m_LastAudioFrameTime = m_pAudioMsg->getTime();
            } catch (Exception &) {
                return pBuffer->getNumFrames()-bufferLeftToFill/pBuffer->getFrameSize();
            }
        }
    }
    return pBuffer->getNumFrames();
}

FrameVideoMsgPtr AsyncVideoDecoder::getBmpsForTime(long long TimeWanted,
        FrameAvailableCode& FrameAvailable)
{
    // XXX: This code is sort-of duplicated in FFMpegDecoder::readFrameForTime()
    long long FrameTime = -1000;
    FrameVideoMsgPtr pFrameMsg;
    if (TimeWanted == -1) {
        pFrameMsg = getNextBmps(true);
        FrameAvailable = FA_NEW_FRAME;
    } else {
        if (getMasterStream() == SS_AUDIO) {
            TimeWanted = m_LastAudioFrameTime;
        }

//        cerr << "getBmpsForTime " << TimeWanted << ", LastFrameTime= " << m_LastVideoFrameTime
//                << ", diff= " << TimeWanted-m_LastVideoFrameTime <<  endl;
        double TimePerFrame = 1000.0/getFPS();
        if (fabs(double(TimeWanted-m_LastVideoFrameTime)) < 0.5*TimePerFrame ||
                m_LastVideoFrameTime > TimeWanted+TimePerFrame) {
//            cerr << "   LastFrameTime = " << m_LastVideoFrameTime << ", display again." <<  endl;
            // The last frame is still current. Display it again.
            FrameAvailable = FA_USE_LAST_FRAME;
            return FrameVideoMsgPtr();
        } else {
            if (m_bVideoEOF) {
//                cerr << "  EOF." << endl;
                FrameAvailable = FA_USE_LAST_FRAME;
                return FrameVideoMsgPtr();
            }
            while (FrameTime-TimeWanted < -0.5*TimePerFrame && !m_bVideoEOF) {
                pFrameMsg = getNextBmps(false);
                if (pFrameMsg) {
                    FrameTime = pFrameMsg->getFrameTime();
//                    cerr << "   readFrame returned time " << FrameTime << "." <<  endl;
                } else {
//                    cerr << "   no frame available." <<  endl;
                    FrameAvailable = FA_STILL_DECODING;
                    return FrameVideoMsgPtr();
                }
            }
            FrameAvailable = FA_NEW_FRAME;
//            cerr << "  frame ok." << endl;
        }
    }
    if (pFrameMsg) {
        m_LastVideoFrameTime = pFrameMsg->getFrameTime();
    }
    return pFrameMsg;
}

FrameVideoMsgPtr AsyncVideoDecoder::getNextBmps(bool bWait)
{
    try {
        waitForSeekDone();
        VideoMsgPtr pMsg = m_pVMsgQ->pop(bWait);
        FrameVideoMsgPtr pFrameMsg = dynamic_pointer_cast<FrameVideoMsg>(pMsg);
        while (!pFrameMsg) {
            EOFVideoMsgPtr pEOFMsg(dynamic_pointer_cast<EOFVideoMsg>(pMsg));
            ErrorVideoMsgPtr pErrorMsg(dynamic_pointer_cast<ErrorVideoMsg>(pMsg));
            if (pEOFMsg) {
                m_bVideoEOF = true;
                return FrameVideoMsgPtr();
            } else if (pErrorMsg) {
                m_bVideoEOF = true;
                return FrameVideoMsgPtr();
            } else {
                // Unhandled message type.
                assert(false);
            }
            pMsg = m_pVMsgQ->pop(bWait);
            pFrameMsg = dynamic_pointer_cast<FrameVideoMsg>(pMsg);
        }
        return pFrameMsg;
    } catch (Exception&) {
        return FrameVideoMsgPtr();
    }
}

void AsyncVideoDecoder::waitForSeekDone()
{
    scoped_lock Lock(m_SeekMutex);
    if (m_bSeekPending) {
        do {
            VideoMsgPtr pMsg;
            if (m_pVCmdQ) {
                pMsg = m_pVMsgQ->pop(true);
            } else {
                pMsg = m_pAMsgQ->pop(true);
            }
            SeekDoneVideoMsgPtr pSeekDoneMsg = dynamic_pointer_cast<SeekDoneVideoMsg>(pMsg);
            if (pSeekDoneMsg) {
                m_bSeekPending = false;
                m_LastVideoFrameTime = pSeekDoneMsg->getVideoFrameTime();
                m_LastAudioFrameTime = pSeekDoneMsg->getAudioFrameTime();
            }

        } while (m_bSeekPending);
    }
}

}
<commit_msg>workaround for audio threading issue<commit_after>//
//  libavg - Media Playback Engine.
//  Copyright (C) 2003-2008 Ulrich von Zadow
//
//  This library 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 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//  Current versions can be found at www.libavg.de
//

#include "AsyncVideoDecoder.h"
#include "EOFVideoMsg.h"
#include "ErrorVideoMsg.h"
#include "SeekDoneVideoMsg.h"

#include "../base/ObjectCounter.h"

#include <boost/thread/thread.hpp>
#include <boost/bind.hpp>

#include <math.h>
#include <iostream>

using namespace boost;
using namespace std;

namespace avg {

AsyncVideoDecoder::AsyncVideoDecoder(VideoDecoderPtr pSyncDecoder)
    : m_pSyncDecoder(pSyncDecoder),
      m_pVDecoderThread(0),
      m_pADecoderThread(0),
      m_Size(0,0),
      m_NumFrames(0),
      m_FPS(0),
      m_PF(NO_PIXELFORMAT),
      m_Duration(0),
      m_bAudioEOF(false),
      m_bVideoEOF(false),
      m_bSeekPending(false),
      m_Volume(1.0),
      m_LastVideoFrameTime(-1000),
      m_LastAudioFrameTime(-1000)
{
    ObjectCounter::get()->incRef(&typeid(*this));
}

AsyncVideoDecoder::~AsyncVideoDecoder()
{
    if (m_pVDecoderThread || m_pADecoderThread) {
        close();
    }
    ObjectCounter::get()->decRef(&typeid(*this));
}

void AsyncVideoDecoder::open(const std::string& sFilename, const AudioParams* pAP,
        YCbCrMode ycbcrMode, bool bThreadedDemuxer)
{
    m_bAudioEOF = false;
    m_bVideoEOF = false;
    m_bSeekPending = false;
    m_sFilename = sFilename;

    m_pSyncDecoder->open(m_sFilename, pAP, ycbcrMode, bThreadedDemuxer);
    m_bHasVideo = m_pSyncDecoder->hasVideo();
    m_bHasAudio = m_pSyncDecoder->hasAudio();
    m_Duration = m_pSyncDecoder->getDuration();

    if (m_bHasVideo) {
        m_LastVideoFrameTime = -1000;
        m_Size = m_pSyncDecoder->getSize();
        m_NumFrames = m_pSyncDecoder->getNumFrames();
        m_FPS = m_pSyncDecoder->getFPS();
        m_PF = m_pSyncDecoder->getPixelFormat();
        m_StreamFPS = m_pSyncDecoder->getNominalFPS();
        m_pVCmdQ = VideoDecoderThread::CmdQueuePtr(new VideoDecoderThread::CmdQueue);
        m_pVMsgQ = VideoMsgQueuePtr(new VideoMsgQueue(8));
        m_pVDecoderThread = new boost::thread(
                 VideoDecoderThread(*m_pVCmdQ, *m_pVMsgQ, m_pSyncDecoder));
    }

    if (m_bHasAudio) {
        m_pACmdQ = AudioDecoderThread::CmdQueuePtr(new AudioDecoderThread::CmdQueue);
        m_pAMsgQ = VideoMsgQueuePtr(new VideoMsgQueue(8));
        m_pADecoderThread = new boost::thread(
                 AudioDecoderThread(*m_pACmdQ, *m_pAMsgQ, m_pSyncDecoder, *pAP));
        m_AudioMsgData = 0;
        m_AudioMsgSize = 0;
        m_LastAudioFrameTime = 0;
    }
}

void AsyncVideoDecoder::close()
{
    if (m_pVDecoderThread) {
        m_pVCmdQ->push(Command<VideoDecoderThread>(boost::bind(
                &VideoDecoderThread::stop, _1)));
        getNextBmps(false); // If the Queue is full, this breaks the lock in the thread.
        m_pVDecoderThread->join();
        delete m_pVDecoderThread;
        m_pVDecoderThread = 0;
    }
    {
        scoped_lock Lock1(m_AudioMutex);
        if (m_pADecoderThread) {
            m_pACmdQ->push(Command<AudioDecoderThread>(boost::bind(
                    &AudioDecoderThread::stop, _1)));
            try {
                m_pAMsgQ->pop(false);
                m_pAMsgQ->pop(false);
            } catch(Exception&) {}
            m_pADecoderThread->join();
            delete m_pADecoderThread;
            m_pADecoderThread = 0;
        }
        m_pSyncDecoder->close();
    }
}

void AsyncVideoDecoder::seek(long long DestTime)
{
    waitForSeekDone();
    scoped_lock Lock1(m_AudioMutex);
    scoped_lock Lock2(m_SeekMutex);
    m_bAudioEOF = false;
    m_bVideoEOF = false;
    m_bSeekPending = false;
    m_LastVideoFrameTime = -1000;
    m_bSeekPending = true;
    if (m_pVCmdQ) {
        m_pVCmdQ->push(Command<VideoDecoderThread>(boost::bind(
                    &VideoDecoderThread::seek, _1, DestTime)));
    } else {
        m_pACmdQ->push(Command<AudioDecoderThread>(boost::bind(
                    &AudioDecoderThread::seek, _1, DestTime)));
    }
    try {
        while (m_bSeekPending) {
            VideoMsgPtr pMsg;
            if (m_pVCmdQ) {
                pMsg = m_pVMsgQ->pop(false);
            } else {
                pMsg = m_pAMsgQ->pop(false);
            }
            SeekDoneVideoMsgPtr pSeekDoneMsg = dynamic_pointer_cast<SeekDoneVideoMsg>(pMsg);
            if (pSeekDoneMsg) {
                m_bSeekPending = false;
                m_LastVideoFrameTime = pSeekDoneMsg->getVideoFrameTime();
                m_LastAudioFrameTime = pSeekDoneMsg->getAudioFrameTime();
            }
        }
    } catch (Exception&) {
    }
}

StreamSelect AsyncVideoDecoder::getMasterStream()
{
    if (m_bHasAudio) {
        return SS_AUDIO;
    } else {
        return SS_VIDEO;
    }
}

bool AsyncVideoDecoder::hasVideo()
{
    return m_bHasVideo;
}

bool AsyncVideoDecoder::hasAudio()
{
    return m_bHasAudio;
}

IntPoint AsyncVideoDecoder::getSize()
{
    return m_Size;
}

int AsyncVideoDecoder::getCurFrame()
{
    return int(getCurTime(SS_VIDEO)*m_StreamFPS/1000.0+0.5);
}

int AsyncVideoDecoder::getNumFrames()
{
    if (m_NumFrames == 0) {
        throw Exception(AVG_ERR_VIDEO_GENERAL, "Error in AsyncVideoDecoder::getNumFrames: Video not loaded.");
    }
    return m_NumFrames;
}

int AsyncVideoDecoder::getNumFramesQueued()
{
    return m_pVMsgQ->size();
}

long long AsyncVideoDecoder::getCurTime(StreamSelect Stream)
{
    switch(Stream) {
        case SS_VIDEO:
            assert(m_bHasVideo);
            return m_LastVideoFrameTime;
            break;
        case SS_AUDIO:
            assert(m_bHasAudio);
            return m_LastAudioFrameTime;
            break;
        case SS_DEFAULT:
            return getCurTime(getMasterStream());
            break;
        default:
            assert(false);
    }
    return -1;
}

long long AsyncVideoDecoder::getDuration()
{
    return m_Duration;
}

double AsyncVideoDecoder::getNominalFPS()
{
    return m_StreamFPS;
}

double AsyncVideoDecoder::getFPS()
{
    assert(m_pVDecoderThread);
    return m_FPS;
}

void AsyncVideoDecoder::setFPS(double FPS)
{
    assert(!m_pADecoderThread);
    m_pVCmdQ->push(Command<VideoDecoderThread>(boost::bind(
            &VideoDecoderThread::setFPS, _1, FPS)));
    if (FPS != 0) {
        m_FPS = FPS;
    }
}

double AsyncVideoDecoder::getVolume()
{
    return m_Volume;
}

void AsyncVideoDecoder::setVolume(double Volume)
{
    m_Volume = Volume;
    if (m_bHasAudio) {
        m_pACmdQ->push(Command<AudioDecoderThread>(boost::bind(
                &AudioDecoderThread::setVolume, _1, Volume)));
    }
}

PixelFormat AsyncVideoDecoder::getPixelFormat()
{
    assert(m_pVDecoderThread);
    return m_PF;
}

FrameAvailableCode AsyncVideoDecoder::renderToBmp(BitmapPtr pBmp, long long TimeWanted)
{
    FrameAvailableCode FrameAvailable;
    FrameVideoMsgPtr pFrameMsg = getBmpsForTime(TimeWanted, FrameAvailable);
    if (FrameAvailable == FA_NEW_FRAME) {
        pBmp->copyPixels(*(pFrameMsg->getBitmap(0)));
    }
    return FrameAvailable;
}

FrameAvailableCode AsyncVideoDecoder::renderToYCbCr420p(BitmapPtr pBmpY, BitmapPtr pBmpCb,
       BitmapPtr pBmpCr, long long TimeWanted)
{
    FrameAvailableCode FrameAvailable;
    FrameVideoMsgPtr pFrameMsg = getBmpsForTime(TimeWanted, FrameAvailable);
    if (FrameAvailable == FA_NEW_FRAME) {
        pBmpY->copyPixels(*(pFrameMsg->getBitmap(0)));
        pBmpCb->copyPixels(*(pFrameMsg->getBitmap(1)));
        pBmpCr->copyPixels(*(pFrameMsg->getBitmap(2)));
    }
    return FrameAvailable;
}

bool AsyncVideoDecoder::isEOF(StreamSelect Stream)
{
    switch(Stream) {
        case SS_AUDIO:
            return (!m_bHasAudio || m_bAudioEOF);
        case SS_VIDEO:
            return (!m_bHasVideo || m_bVideoEOF);
        case SS_ALL:
            return isEOF(SS_VIDEO) && isEOF(SS_AUDIO);
        default:
            return false;
    }
}

int AsyncVideoDecoder::fillAudioBuffer(AudioBufferPtr pBuffer)
{
    assert (m_pADecoderThread);
    if (m_bAudioEOF) {
        return 0;
    }
    scoped_lock Lock(m_AudioMutex);
    waitForSeekDone();

    unsigned char* audioBuffer = (unsigned char *)(pBuffer->getData());
    int audioBufferSize = pBuffer->getNumBytes();

    int bufferLeftToFill = audioBufferSize;
    while (bufferLeftToFill > 0) {
        while (m_AudioMsgSize > 0 && bufferLeftToFill > 0) {
            int copyBytes = min(bufferLeftToFill, m_AudioMsgSize);
            memcpy(audioBuffer, m_AudioMsgData, copyBytes);
            m_AudioMsgSize -= copyBytes;
            m_AudioMsgData += copyBytes;
            bufferLeftToFill -= copyBytes;
            audioBuffer += copyBytes;

            m_LastAudioFrameTime += (long long)(1000.0 * copyBytes /
                    (pBuffer->getFrameSize() * pBuffer->getRate()));
        }
        if (bufferLeftToFill != 0) {
            try {
                VideoMsgPtr pMsg = m_pAMsgQ->pop(false);

                EOFVideoMsgPtr pEOFMsg(dynamic_pointer_cast<EOFVideoMsg>(pMsg));
                if (pEOFMsg) {
                    m_bAudioEOF = true;
                    return pBuffer->getNumFrames()-bufferLeftToFill/pBuffer->getFrameSize();
                }

                m_pAudioMsg = dynamic_pointer_cast<AudioVideoMsg>(pMsg);
                assert(m_pAudioMsg);

                m_AudioMsgSize = m_pAudioMsg->getBuffer()->getNumFrames()
                        *pBuffer->getFrameSize();
                m_AudioMsgData = (unsigned char *)(m_pAudioMsg->getBuffer()->getData());
                m_LastAudioFrameTime = m_pAudioMsg->getTime();
            } catch (Exception &) {
                return pBuffer->getNumFrames()-bufferLeftToFill/pBuffer->getFrameSize();
            }
        }
    }
    return pBuffer->getNumFrames();
}

FrameVideoMsgPtr AsyncVideoDecoder::getBmpsForTime(long long TimeWanted,
        FrameAvailableCode& FrameAvailable)
{
    // XXX: This code is sort-of duplicated in FFMpegDecoder::readFrameForTime()
    long long FrameTime = -1000;
    FrameVideoMsgPtr pFrameMsg;
    if (TimeWanted == -1) {
        pFrameMsg = getNextBmps(true);
        FrameAvailable = FA_NEW_FRAME;
    } else {
        if (getMasterStream() == SS_AUDIO) {
            TimeWanted = m_LastAudioFrameTime;
        }

//        cerr << "getBmpsForTime " << TimeWanted << ", LastFrameTime= " << m_LastVideoFrameTime
//                << ", diff= " << TimeWanted-m_LastVideoFrameTime <<  endl;
        double TimePerFrame = 1000.0/getFPS();
        if (fabs(double(TimeWanted-m_LastVideoFrameTime)) < 0.5*TimePerFrame ||
                m_LastVideoFrameTime > TimeWanted+TimePerFrame) {
//            cerr << "   LastFrameTime = " << m_LastVideoFrameTime << ", display again." <<  endl;
            // The last frame is still current. Display it again.
            FrameAvailable = FA_USE_LAST_FRAME;
            return FrameVideoMsgPtr();
        } else {
            if (m_bVideoEOF) {
//                cerr << "  EOF." << endl;
                FrameAvailable = FA_USE_LAST_FRAME;
                return FrameVideoMsgPtr();
            }
            while (FrameTime-TimeWanted < -0.5*TimePerFrame && !m_bVideoEOF) {
                pFrameMsg = getNextBmps(false);
                if (pFrameMsg) {
                    FrameTime = pFrameMsg->getFrameTime();
//                    cerr << "   readFrame returned time " << FrameTime << "." <<  endl;
                } else {
//                    cerr << "   no frame available." <<  endl;
                    FrameAvailable = FA_STILL_DECODING;
                    return FrameVideoMsgPtr();
                }
            }
            FrameAvailable = FA_NEW_FRAME;
//            cerr << "  frame ok." << endl;
        }
    }
    if (pFrameMsg) {
        m_LastVideoFrameTime = pFrameMsg->getFrameTime();
    }
    return pFrameMsg;
}

FrameVideoMsgPtr AsyncVideoDecoder::getNextBmps(bool bWait)
{
    try {
        waitForSeekDone();
        VideoMsgPtr pMsg = m_pVMsgQ->pop(bWait);
        FrameVideoMsgPtr pFrameMsg = dynamic_pointer_cast<FrameVideoMsg>(pMsg);
        while (!pFrameMsg) {
            EOFVideoMsgPtr pEOFMsg(dynamic_pointer_cast<EOFVideoMsg>(pMsg));
            ErrorVideoMsgPtr pErrorMsg(dynamic_pointer_cast<ErrorVideoMsg>(pMsg));
            if (pEOFMsg) {
                m_bVideoEOF = true;
                return FrameVideoMsgPtr();
            } else if (pErrorMsg) {
                m_bVideoEOF = true;
                return FrameVideoMsgPtr();
            } else {
                // Unhandled message type.
                assert(false);
            }
            pMsg = m_pVMsgQ->pop(bWait);
            pFrameMsg = dynamic_pointer_cast<FrameVideoMsg>(pMsg);
        }
        return pFrameMsg;
    } catch (Exception&) {
        return FrameVideoMsgPtr();
    }
}

void AsyncVideoDecoder::waitForSeekDone()
{
    scoped_lock Lock(m_SeekMutex);
    if (m_bSeekPending) {
        do {
            VideoMsgPtr pMsg;
            if (m_pVCmdQ) {
                pMsg = m_pVMsgQ->pop(true);
            } else {
                pMsg = m_pAMsgQ->pop(true);
            }
            SeekDoneVideoMsgPtr pSeekDoneMsg = dynamic_pointer_cast<SeekDoneVideoMsg>(pMsg);
            if (pSeekDoneMsg) {
                m_bSeekPending = false;
                m_LastVideoFrameTime = pSeekDoneMsg->getVideoFrameTime();
                m_LastAudioFrameTime = pSeekDoneMsg->getAudioFrameTime();
            }

        } while (m_bSeekPending);
    }
}

}
<|endoftext|>


<commit_before>/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2013 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#include "xenia/base/logging.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/objects/xthread.h"
#include "xenia/kernel/util/shim_utils.h"
#include "xenia/kernel/xboxkrnl_private.h"
#include "xenia/kernel/xobject.h"
#include "xenia/xbox.h"

namespace xe {
namespace kernel {

SHIM_CALL ObOpenObjectByName_shim(PPCContext* ppc_context,
                                  KernelState* kernel_state) {
  // r3 = ptr to info?
  //   +0 = -4
  //   +4 = name ptr
  //   +8 = 0
  // r4 = ExEventObjectType | ExSemaphoreObjectType | ExTimerObjectType
  // r5 = 0
  // r6 = out_ptr (handle?)
  uint32_t obj_attributes_ptr = SHIM_GET_ARG_32(0);
  uint32_t object_type_ptr = SHIM_GET_ARG_32(1);
  uint32_t unk = SHIM_GET_ARG_32(2);
  uint32_t handle_ptr = SHIM_GET_ARG_32(3);

  uint32_t name_str_ptr = SHIM_MEM_32(obj_attributes_ptr + 4);
  X_ANSI_STRING name_str(SHIM_MEM_BASE, name_str_ptr);
  auto name = name_str.to_string();

  XELOGD("ObOpenObjectByName(%.8X(name=%s), %.8X, %.8X, %.8X)",
         obj_attributes_ptr, name.c_str(), object_type_ptr, unk, handle_ptr);

  X_HANDLE handle = X_INVALID_HANDLE_VALUE;
  X_STATUS result =
      kernel_state->object_table()->GetObjectByName(name, &handle);
  if (XSUCCEEDED(result)) {
    SHIM_SET_MEM_32(handle_ptr, handle);
  }

  SHIM_SET_RETURN_32(result);
}

SHIM_CALL ObReferenceObjectByHandle_shim(PPCContext* ppc_context,
                                         KernelState* kernel_state) {
  uint32_t handle = SHIM_GET_ARG_32(0);
  uint32_t object_type_ptr = SHIM_GET_ARG_32(1);
  uint32_t out_object_ptr = SHIM_GET_ARG_32(2);

  XELOGD("ObReferenceObjectByHandle(%.8X, %.8X, %.8X)", handle, object_type_ptr,
         out_object_ptr);

  X_STATUS result = X_STATUS_SUCCESS;

  auto object = kernel_state->object_table()->LookupObject<XObject>(handle);
  if (object) {
    // TODO(benvanik): verify type with object_type_ptr

    // TODO(benvanik): get native value, if supported.
    uint32_t native_ptr;
    switch (object_type_ptr) {
      case 0x00000000: {  // whatever?
        switch (object->type()) {
          // TODO(benvanik): need to track native_ptr in XObject, allocate as
          // needed?
          /*case XObject::kTypeEvent: {
            XEvent* ev = (XEvent*)object;
          } break;*/
          case XObject::kTypeThread: {
            auto thread = object.get<XThread>();
            native_ptr = thread->thread_state_ptr();
          } break;
          default: {
            assert_unhandled_case(object->type());
            native_ptr = 0xDEADF00D;
          } break;
        }
      } break;
      case 0xD017BEEF: {  // ExSemaphoreObjectType
        // TODO(benvanik): implement.
        assert_unhandled_case(object_type_ptr);
        native_ptr = 0xDEADF00D;
      } break;
      case 0xD01BBEEF: {  // ExThreadObjectType
        auto thread = object.get<XThread>();
        native_ptr = thread->thread_state_ptr();
      } break;
      default: {
        assert_unhandled_case(object_type_ptr);
        native_ptr = 0xDEADF00D;
      } break;
    }

    // Caller takes the reference.
    // It's released in ObDereferenceObject.
    object->Retain();
    if (out_object_ptr) {
      SHIM_SET_MEM_32(out_object_ptr, native_ptr);
    }
  } else {
    result = X_STATUS_INVALID_HANDLE;
  }

  SHIM_SET_RETURN_32(result);
}

SHIM_CALL ObDereferenceObject_shim(PPCContext* ppc_context,
                                   KernelState* kernel_state) {
  uint32_t native_ptr = SHIM_GET_ARG_32(0);

  XELOGD("ObDereferenceObject(%.8X)", native_ptr);

  // Check if a dummy value from ObReferenceObjectByHandle.
  if (native_ptr == 0xDEADF00D) {
    SHIM_SET_RETURN_32(0);
    return;
  }

  void* object_ptr = SHIM_MEM_ADDR(native_ptr);
  auto object = XObject::GetNativeObject<XObject>(kernel_state, object_ptr);
  if (object) {
    object->Release();
  }

  SHIM_SET_RETURN_32(0);
}

dword_result_t NtDuplicateObject(dword_t handle, lpdword_t new_handle_ptr,
                                 dword_t options) {
  // NOTE: new_handle_ptr can be zero to just close a handle.
  // NOTE: this function seems to be used to get the current thread handle
  //       (passed handle=-2).
  // This function actually just creates a new handle to the same object.
  // Most games use it to get real handles to the current thread or whatever.

  X_HANDLE new_handle = X_INVALID_HANDLE_VALUE;
  X_STATUS result =
      kernel_state()->object_table()->DuplicateHandle(handle, &new_handle);

  if (options == 1 /* DUPLICATE_CLOSE_SOURCE */) {
    // Always close the source object.
    kernel_state()->object_table()->RemoveHandle(handle);
  }

  return result;
}
DECLARE_XBOXKRNL_EXPORT(NtDuplicateObject, ExportTag::kImplemented);

dword_result_t NtClose(dword_t handle) {
  return kernel_state()->object_table()->RemoveHandle(handle);
}
DECLARE_XBOXKRNL_EXPORT(NtClose, ExportTag::kImplemented);

}  // namespace kernel
}  // namespace xe

void xe::kernel::xboxkrnl::RegisterObExports(
    xe::cpu::ExportResolver* export_resolver, KernelState* kernel_state) {
  SHIM_SET_MAPPING("xboxkrnl.exe", ObOpenObjectByName, state);
  SHIM_SET_MAPPING("xboxkrnl.exe", ObReferenceObjectByHandle, state);
  SHIM_SET_MAPPING("xboxkrnl.exe", ObDereferenceObject, state);
}
<commit_msg>Actually give the game the new handle<commit_after>/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2013 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#include "xenia/base/logging.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/objects/xthread.h"
#include "xenia/kernel/util/shim_utils.h"
#include "xenia/kernel/xboxkrnl_private.h"
#include "xenia/kernel/xobject.h"
#include "xenia/xbox.h"

namespace xe {
namespace kernel {

SHIM_CALL ObOpenObjectByName_shim(PPCContext* ppc_context,
                                  KernelState* kernel_state) {
  // r3 = ptr to info?
  //   +0 = -4
  //   +4 = name ptr
  //   +8 = 0
  // r4 = ExEventObjectType | ExSemaphoreObjectType | ExTimerObjectType
  // r5 = 0
  // r6 = out_ptr (handle?)
  uint32_t obj_attributes_ptr = SHIM_GET_ARG_32(0);
  uint32_t object_type_ptr = SHIM_GET_ARG_32(1);
  uint32_t unk = SHIM_GET_ARG_32(2);
  uint32_t handle_ptr = SHIM_GET_ARG_32(3);

  uint32_t name_str_ptr = SHIM_MEM_32(obj_attributes_ptr + 4);
  X_ANSI_STRING name_str(SHIM_MEM_BASE, name_str_ptr);
  auto name = name_str.to_string();

  XELOGD("ObOpenObjectByName(%.8X(name=%s), %.8X, %.8X, %.8X)",
         obj_attributes_ptr, name.c_str(), object_type_ptr, unk, handle_ptr);

  X_HANDLE handle = X_INVALID_HANDLE_VALUE;
  X_STATUS result =
      kernel_state->object_table()->GetObjectByName(name, &handle);
  if (XSUCCEEDED(result)) {
    SHIM_SET_MEM_32(handle_ptr, handle);
  }

  SHIM_SET_RETURN_32(result);
}

SHIM_CALL ObReferenceObjectByHandle_shim(PPCContext* ppc_context,
                                         KernelState* kernel_state) {
  uint32_t handle = SHIM_GET_ARG_32(0);
  uint32_t object_type_ptr = SHIM_GET_ARG_32(1);
  uint32_t out_object_ptr = SHIM_GET_ARG_32(2);

  XELOGD("ObReferenceObjectByHandle(%.8X, %.8X, %.8X)", handle, object_type_ptr,
         out_object_ptr);

  X_STATUS result = X_STATUS_SUCCESS;

  auto object = kernel_state->object_table()->LookupObject<XObject>(handle);
  if (object) {
    // TODO(benvanik): verify type with object_type_ptr

    // TODO(benvanik): get native value, if supported.
    uint32_t native_ptr;
    switch (object_type_ptr) {
      case 0x00000000: {  // whatever?
        switch (object->type()) {
          // TODO(benvanik): need to track native_ptr in XObject, allocate as
          // needed?
          /*case XObject::kTypeEvent: {
            XEvent* ev = (XEvent*)object;
          } break;*/
          case XObject::kTypeThread: {
            auto thread = object.get<XThread>();
            native_ptr = thread->thread_state_ptr();
          } break;
          default: {
            assert_unhandled_case(object->type());
            native_ptr = 0xDEADF00D;
          } break;
        }
      } break;
      case 0xD017BEEF: {  // ExSemaphoreObjectType
        // TODO(benvanik): implement.
        assert_unhandled_case(object_type_ptr);
        native_ptr = 0xDEADF00D;
      } break;
      case 0xD01BBEEF: {  // ExThreadObjectType
        auto thread = object.get<XThread>();
        native_ptr = thread->thread_state_ptr();
      } break;
      default: {
        assert_unhandled_case(object_type_ptr);
        native_ptr = 0xDEADF00D;
      } break;
    }

    // Caller takes the reference.
    // It's released in ObDereferenceObject.
    object->Retain();
    if (out_object_ptr) {
      SHIM_SET_MEM_32(out_object_ptr, native_ptr);
    }
  } else {
    result = X_STATUS_INVALID_HANDLE;
  }

  SHIM_SET_RETURN_32(result);
}

SHIM_CALL ObDereferenceObject_shim(PPCContext* ppc_context,
                                   KernelState* kernel_state) {
  uint32_t native_ptr = SHIM_GET_ARG_32(0);

  XELOGD("ObDereferenceObject(%.8X)", native_ptr);

  // Check if a dummy value from ObReferenceObjectByHandle.
  if (native_ptr == 0xDEADF00D) {
    SHIM_SET_RETURN_32(0);
    return;
  }

  void* object_ptr = SHIM_MEM_ADDR(native_ptr);
  auto object = XObject::GetNativeObject<XObject>(kernel_state, object_ptr);
  if (object) {
    object->Release();
  }

  SHIM_SET_RETURN_32(0);
}

dword_result_t NtDuplicateObject(dword_t handle, lpdword_t new_handle_ptr,
                                 dword_t options) {
  // NOTE: new_handle_ptr can be zero to just close a handle.
  // NOTE: this function seems to be used to get the current thread handle
  //       (passed handle=-2).
  // This function actually just creates a new handle to the same object.
  // Most games use it to get real handles to the current thread or whatever.

  X_HANDLE new_handle = X_INVALID_HANDLE_VALUE;
  X_STATUS result =
      kernel_state()->object_table()->DuplicateHandle(handle, &new_handle);

  if (new_handle_ptr) {
    *new_handle_ptr = new_handle;
  }

  if (options == 1 /* DUPLICATE_CLOSE_SOURCE */) {
    // Always close the source object.
    kernel_state()->object_table()->RemoveHandle(handle);
  }

  return result;
}
DECLARE_XBOXKRNL_EXPORT(NtDuplicateObject, ExportTag::kImplemented);

dword_result_t NtClose(dword_t handle) {
  return kernel_state()->object_table()->RemoveHandle(handle);
}
DECLARE_XBOXKRNL_EXPORT(NtClose, ExportTag::kImplemented);

}  // namespace kernel
}  // namespace xe

void xe::kernel::xboxkrnl::RegisterObExports(
    xe::cpu::ExportResolver* export_resolver, KernelState* kernel_state) {
  SHIM_SET_MAPPING("xboxkrnl.exe", ObOpenObjectByName, state);
  SHIM_SET_MAPPING("xboxkrnl.exe", ObReferenceObjectByHandle, state);
  SHIM_SET_MAPPING("xboxkrnl.exe", ObDereferenceObject, state);
}
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015 - The CM Authors <legal@clickmatcher.com>
 *   All Rights Reserved.
 *
 * This file is CONFIDENTIAL -- Distribution or duplication of this material or
 * the information contained herein is strictly forbidden unless prior written
 * permission is obtained.
 */
#include <unistd.h>
#include "zbase/api/LogfileService.h"
#include "stx/RegExp.h"
#include "stx/human.h"
#include "stx/protobuf/msg.h"
#include "stx/protobuf/MessageSchema.h"
#include "stx/protobuf/MessagePrinter.h"
#include "stx/protobuf/MessageEncoder.h"
#include "stx/protobuf/DynamicMessage.h"
#include "csql/qtree/SelectListNode.h"
#include "csql/qtree/ColumnReferenceNode.h"
#include "csql/CSTableScan.h"
#include "zbase/core/TimeWindowPartitioner.h"

using namespace stx;

namespace zbase {

LogfileService::LogfileService(
    ConfigDirectory* cdir,
    AnalyticsAuth* auth,
    zbase::TSDBService* tsdb,
    zbase::PartitionMap* pmap,
    zbase::ReplicationScheme* repl,
    csql::Runtime* sql) :
    cdir_(cdir),
    auth_(auth),
    tsdb_(tsdb),
    pmap_(pmap),
    repl_(repl),
    sql_(sql) {}

void LogfileService::scanLogfile(
    const AnalyticsSession& session,
    const String& logfile_name,
    const LogfileScanParams& params,
    LogfileScanResult* result,
    Function<void (bool done)> on_progress) {
  auto logfile_definition = findLogfileDefinition(
      session.customer(), logfile_name);
  if (logfile_definition.isEmpty()) {
    RAISEF(kNotFoundError, "logfile not found: $0", logfile_name);
  }

  auto table_name = "logs." + logfile_name;
  auto lookback_limit = params.end_time() - 1 * kMicrosPerDay;
  auto partition_size = 10 * kMicrosPerMinute;

  result->setColumns(
      Vector<String>(params.columns().begin(), params.columns().end()));

  iputs("end time: $0", UnixTime(params.end_time()));

  for (auto time = params.end_time();
      time > lookback_limit;
      time -= partition_size) {

    auto partition = zbase::TimeWindowPartitioner::partitionKeyFor(
        table_name,
        time,
        partition_size);

    iputs("scan time: $0 -> $1", UnixTime(time), partition.toString());

    if (repl_->hasLocalReplica(partition)) {
      scanLocalLogfilePartition(
          session,
          table_name,
          partition,
          params,
          result);
    } else {
      scanRemoteLogfilePartition(
          session,
          table_name,
          partition,
          params,
          repl_->replicaAddrsFor(partition),
          result);
    }

    result->setScannedUntil(time);

    bool done = result->isFull();
    on_progress(done);

    if (done) {
      break;
    }
  }
}

void LogfileService::scanLocalLogfilePartition(
    const AnalyticsSession& session,
    const String& table_name,
    const SHA1Hash& partition_key,
    const LogfileScanParams& params,
    LogfileScanResult* result) {
  auto partition = pmap_->findPartition(
      session.customer(),
      table_name,
      partition_key);

  if (partition.isEmpty()) {
    return;
  }

  logDebug(
      "zbase",
      "Scanning local logfile partition $0/$1/$2",
      session.customer(),
      table_name,
      partition_key.toString());

  Vector<RefPtr<csql::SelectListNode>> select_list;
  select_list.emplace_back(
      new csql::SelectListNode(
          new csql::ColumnReferenceNode("time")));

  if (params.return_raw()) {
    select_list.emplace_back(
        new csql::SelectListNode(
            new csql::ColumnReferenceNode("raw")));
  }

  for (const auto& c : params.columns()) {
    select_list.emplace_back(
        new csql::SelectListNode(
            new csql::ColumnReferenceNode(c)));
  }

  Option<RefPtr<csql::ValueExpressionNode>> where_cond;
  switch (params.scan_type()) {

    case LOGSCAN_SQL: {
      const auto& sql_str = params.condition();

      csql::Parser parser;
      parser.parseValueExpression(sql_str.data(), sql_str.size());

      auto stmts = parser.getStatements();
      if (stmts.size() != 1) {
        RAISE(
            kParseError,
            "SQL filter expression must consist of exactly one statement");
      }

      where_cond = Some(
          mkRef(sql_->queryPlanBuilder()->buildValueExpression(stmts[0])));

      break;
    }

  }

  auto seqscan = mkRef(
      new csql::SequentialScanNode(
          table_name,
          select_list,
          where_cond));

  auto reader = partition.get()->getReader();
  auto cstable_filename = reader->cstableFilename();
  if (cstable_filename.isEmpty()) {
    return;
  }

  csql::CSTableScan cstable_scan(
      seqscan,
      cstable_filename.get(),
      sql_->queryBuilder().get());

  csql::ExecutionContext context(sql_->scheduler());

  cstable_scan.execute(
      &context,
      [result, &params] (int argc, const csql::SValue* argv) -> bool {
    int colidx = 0;

    auto time = UnixTime(argv[colidx++].getInteger());
    if (time >= params.end_time()) {
      return true;
    }

    auto line = result->addLine(time);
    if (!line) {
      return true;
    }

    if (params.return_raw()) {
      line->raw = argv[colidx++].toString();
    }

    for (; colidx < argc; ++colidx) {
      line->columns.emplace_back(argv[colidx].toString());
    }

    return true;
  });

  result->incrRowsScanned(cstable_scan.rowsScanned());
}

void LogfileService::scanRemoteLogfilePartition(
    const AnalyticsSession& session,
    const String& table_name,
    const SHA1Hash& partition_key,
    const LogfileScanParams& params,
    const Vector<InetAddr>& hosts,
    LogfileScanResult* result) {
  Vector<String> errors;

  for (const auto& host : hosts) {
    try {
      if(scanRemoteLogfilePartition(
          session,
          table_name,
          partition_key,
          params,
          host,
          result)) {
        return;
      }
    } catch (const StandardException& e) {
      logError(
          "zbase",
          e,
          "LogfileService::scanRemoteLogfilePartition failed");

      errors.emplace_back(e.what());
    }
  }

  if (!errors.empty()) {
    RAISEF(
        kRuntimeError,
        "LogfileService::scanRemoteLogfilePartition failed: $0",
        StringUtil::join(errors, ", "));
  }
}

bool LogfileService::scanRemoteLogfilePartition(
    const AnalyticsSession& session,
    const String& table_name,
    const SHA1Hash& partition_key,
    const LogfileScanParams& params,
    const InetAddr& host,
    LogfileScanResult* result) {
  logDebug(
      "zbase",
      "Scanning remote logfile partition $0/$1/$2 on $3",
      session.customer(),
      table_name,
      partition_key.toString(),
      host.hostAndPort());

  auto url = StringUtil::format(
      "http://$0/api/v1/logfiles/scan_partition?table=$1&partition=$2&limit=$3",
      host.hostAndPort(),
      URI::urlEncode(table_name),
      partition_key.toString(),
      result->capacity());

  auto api_token = auth_->encodeAuthToken(session);

  http::HTTPMessage::HeaderList auth_headers;
  auth_headers.emplace_back(
      "Authorization",
      StringUtil::format("Token $0", api_token));

  http::HTTPClient http_client;
  auto req_body = msg::encode(params);
  auto req = http::HTTPRequest::mkPost(url, *req_body, auth_headers);
  auto res = http_client.executeRequest(req);

  if (res.statusCode() == 404) {
    iputs("got 404..", 1);
    return false;
  }

  if (res.statusCode() != 200) {
    RAISEF(
        kRuntimeError,
        "received non-200 response: $0", res.body().toString());
  }

  iputs("got $0 bytes", res.body().size());
  auto body_is = BufferInputStream::fromBuffer(&res.body());
  result->decode(body_is.get());

  return true;
}

void LogfileService::insertLoglines(
    const String& customer,
    const String& logfile_name,
    const Vector<Pair<String, String>>& source_fields,
    InputStream* is) {
  auto logfile_definition = findLogfileDefinition(customer, logfile_name);
  if (logfile_definition.isEmpty()) {
    RAISEF(kNotFoundError, "logfile not found: $0", logfile_name);
  }

  insertLoglines(
      customer,
      logfile_definition.get(),
      source_fields,
      is);
}

void LogfileService::insertLoglines(
    const String& customer,
    const LogfileDefinition& logfile,
    const Vector<Pair<String, String>>& source_fields,
    InputStream* is) {
  String line;

  auto table_name = "logs." + logfile.name();
  auto schema = tsdb_->tableSchema(customer, table_name);
  auto partitioner = tsdb_->tablePartitioner(customer, table_name);
  if (schema.isEmpty() || partitioner.isEmpty()) {
    RAISEF(kNotFoundError, "table not found: $0", table_name);
  }

  msg::DynamicMessage row_base(schema.get());
  for (const auto& f : source_fields) {
    row_base.addField(f.first, f.second);
  }

  RegExp regex(logfile.regex());

  HashMap<size_t, LogfileField> match_fields;
  size_t time_idx = -1;
  String time_format;
  for (const auto& f : logfile.row_fields()) {
    auto match_idx = regex.getNamedCaptureIndex(f.name());
    if (match_idx != size_t(-1)) {
      match_fields.emplace(match_idx, f);
      if (f.name() == "time") {
        time_idx = match_idx;
        time_format = f.format();
      }
    }
  }

  if (time_idx == size_t(-1)) {
    RAISE(kIllegalStateError, "can't import logfile row without time column");
  }

  for (; is->readLine(&line); line.clear()) {
    Vector<Pair<const char*, size_t>> match;
    if (!regex.match(line, &match)) {
      continue;
    }

    Option<UnixTime> time;
    if (time_format.empty()) {
      time = Human::parseTime(
          String(match[time_idx].first, match[time_idx].second));
    } else {
      time = UnixTime::parseString(
          match[time_idx].first,
          match[time_idx].second,
          time_format.c_str());
    }

    if (time.isEmpty()) {
      continue;
    }

    auto row = row_base;
    row.addField("raw", line);

    for (size_t i = 0; i < match.size(); ++i) {
      auto mfield = match_fields.find(i);
      if (mfield == match_fields.end()) {
        continue;
      }

      if (mfield->second.has_format() &&
          mfield->second.type() == "DATETIME") {
        auto t = UnixTime::parseString(
            match[i].first,
            match[i].second,
            mfield->second.format().c_str());

        if (!t.isEmpty()) {
          row.addDateTimeField(mfield->second.id(), t.get());
        }
        continue;
      }

      row.addField(
          mfield->second.id(),
          String(match[i].first, match[i].second));
    }

    Buffer row_buf;
    msg::MessageEncoder::encode(row.data(), *row.schema(), &row_buf);

    auto record_id = Random::singleton()->sha1();
    auto partition_key = partitioner.get()->partitionKeyFor(
        StringUtil::toString(time.get().unixMicros()));

    tsdb_->insertRecord(
        customer,
        table_name,
        partition_key,
        record_id,
        row_buf);
  }
}

Option<LogfileDefinition> LogfileService::findLogfileDefinition(
    const String& customer,
    const String& logfile_name) {
  auto cconf = cdir_->configFor(customer);

  for (const auto& logfile : cconf->config.logfile_import_config().logfiles()) {
    if (logfile.name() == logfile_name) {
      return Some(logfile);
    }
  }

  return None<LogfileDefinition>();
}

RefPtr<msg::MessageSchema> LogfileService::getSchema(
    const LogfileDefinition& cfg) {
  Vector<msg::MessageSchemaField> fields;

  fields.emplace_back(
      msg::MessageSchemaField(
          1,
          "raw",
          msg::FieldType::STRING,
          0,
          false,
          true));

  for (const auto& field : cfg.source_fields()) {
    fields.emplace_back(
        msg::MessageSchemaField(
            field.id(),
            field.name(),
            msg::fieldTypeFromString(field.type()),
            0,
            false,
            true));
  }

  for (const auto& field : cfg.row_fields()) {
    fields.emplace_back(
        msg::MessageSchemaField(
            field.id(),
            field.name(),
            msg::fieldTypeFromString(field.type()),
            0,
            false,
            true));
  }

  return new msg::MessageSchema(cfg.name(), fields);
}

Vector<TableDefinition> LogfileService::getTableDefinitions(
    const CustomerConfig& cfg) {
  Vector<TableDefinition> tbls;

  if (!cfg.has_logfile_import_config()) {
    return tbls;
  }

  for (const auto& logfile : cfg.logfile_import_config().logfiles()) {
    TableDefinition td;
    td.set_customer(cfg.customer());
    td.set_table_name("logs." + logfile.name());
    auto tblcfg = td.mutable_config();
    tblcfg->set_schema(getSchema(logfile)->encode().toString());
    tblcfg->set_partitioner(zbase::TBL_PARTITION_TIMEWINDOW);
    tblcfg->set_storage(zbase::TBL_STORAGE_LOG);

    auto partcfg = tblcfg->mutable_time_window_partitioner_config();
    partcfg->set_partition_size(10 * kMicrosPerMinute);

    tbls.emplace_back(td);
  }

  return tbls;
}

} // namespace zbase
<commit_msg>Revert "debug"<commit_after>/**
 * Copyright (c) 2015 - The CM Authors <legal@clickmatcher.com>
 *   All Rights Reserved.
 *
 * This file is CONFIDENTIAL -- Distribution or duplication of this material or
 * the information contained herein is strictly forbidden unless prior written
 * permission is obtained.
 */
#include <unistd.h>
#include "zbase/api/LogfileService.h"
#include "stx/RegExp.h"
#include "stx/human.h"
#include "stx/protobuf/msg.h"
#include "stx/protobuf/MessageSchema.h"
#include "stx/protobuf/MessagePrinter.h"
#include "stx/protobuf/MessageEncoder.h"
#include "stx/protobuf/DynamicMessage.h"
#include "csql/qtree/SelectListNode.h"
#include "csql/qtree/ColumnReferenceNode.h"
#include "csql/CSTableScan.h"
#include "zbase/core/TimeWindowPartitioner.h"

using namespace stx;

namespace zbase {

LogfileService::LogfileService(
    ConfigDirectory* cdir,
    AnalyticsAuth* auth,
    zbase::TSDBService* tsdb,
    zbase::PartitionMap* pmap,
    zbase::ReplicationScheme* repl,
    csql::Runtime* sql) :
    cdir_(cdir),
    auth_(auth),
    tsdb_(tsdb),
    pmap_(pmap),
    repl_(repl),
    sql_(sql) {}

void LogfileService::scanLogfile(
    const AnalyticsSession& session,
    const String& logfile_name,
    const LogfileScanParams& params,
    LogfileScanResult* result,
    Function<void (bool done)> on_progress) {
  auto logfile_definition = findLogfileDefinition(
      session.customer(), logfile_name);
  if (logfile_definition.isEmpty()) {
    RAISEF(kNotFoundError, "logfile not found: $0", logfile_name);
  }

  auto table_name = "logs." + logfile_name;
  auto lookback_limit = params.end_time() - 90 * kMicrosPerDay;
  auto partition_size = 10 * kMicrosPerMinute;

  result->setColumns(
      Vector<String>(params.columns().begin(), params.columns().end()));

  for (auto time = params.end_time();
      time > lookback_limit;
      time -= partition_size) {

    auto partition = zbase::TimeWindowPartitioner::partitionKeyFor(
        table_name,
        time,
        partition_size);

    if (repl_->hasLocalReplica(partition)) {
      scanLocalLogfilePartition(
          session,
          table_name,
          partition,
          params,
          result);
    } else {
      scanRemoteLogfilePartition(
          session,
          table_name,
          partition,
          params,
          repl_->replicaAddrsFor(partition),
          result);
    }

    result->setScannedUntil(time);

    bool done = result->isFull();
    on_progress(done);

    if (done) {
      break;
    }
  }
}

void LogfileService::scanLocalLogfilePartition(
    const AnalyticsSession& session,
    const String& table_name,
    const SHA1Hash& partition_key,
    const LogfileScanParams& params,
    LogfileScanResult* result) {
  auto partition = pmap_->findPartition(
      session.customer(),
      table_name,
      partition_key);

  if (partition.isEmpty()) {
    return;
  }

  logDebug(
      "zbase",
      "Scanning local logfile partition $0/$1/$2",
      session.customer(),
      table_name,
      partition_key.toString());

  Vector<RefPtr<csql::SelectListNode>> select_list;
  select_list.emplace_back(
      new csql::SelectListNode(
          new csql::ColumnReferenceNode("time")));

  if (params.return_raw()) {
    select_list.emplace_back(
        new csql::SelectListNode(
            new csql::ColumnReferenceNode("raw")));
  }

  for (const auto& c : params.columns()) {
    select_list.emplace_back(
        new csql::SelectListNode(
            new csql::ColumnReferenceNode(c)));
  }

  Option<RefPtr<csql::ValueExpressionNode>> where_cond;
  switch (params.scan_type()) {

    case LOGSCAN_SQL: {
      const auto& sql_str = params.condition();

      csql::Parser parser;
      parser.parseValueExpression(sql_str.data(), sql_str.size());

      auto stmts = parser.getStatements();
      if (stmts.size() != 1) {
        RAISE(
            kParseError,
            "SQL filter expression must consist of exactly one statement");
      }

      where_cond = Some(
          mkRef(sql_->queryPlanBuilder()->buildValueExpression(stmts[0])));

      break;
    }

  }

  auto seqscan = mkRef(
      new csql::SequentialScanNode(
          table_name,
          select_list,
          where_cond));

  auto reader = partition.get()->getReader();
  auto cstable_filename = reader->cstableFilename();
  if (cstable_filename.isEmpty()) {
    return;
  }

  csql::CSTableScan cstable_scan(
      seqscan,
      cstable_filename.get(),
      sql_->queryBuilder().get());

  csql::ExecutionContext context(sql_->scheduler());

  cstable_scan.execute(
      &context,
      [result, &params] (int argc, const csql::SValue* argv) -> bool {
    int colidx = 0;

    auto time = UnixTime(argv[colidx++].getInteger());
    if (time >= params.end_time()) {
      return true;
    }

    auto line = result->addLine(time);
    if (!line) {
      return true;
    }

    if (params.return_raw()) {
      line->raw = argv[colidx++].toString();
    }

    for (; colidx < argc; ++colidx) {
      line->columns.emplace_back(argv[colidx].toString());
    }

    return true;
  });

  result->incrRowsScanned(cstable_scan.rowsScanned());
}

void LogfileService::scanRemoteLogfilePartition(
    const AnalyticsSession& session,
    const String& table_name,
    const SHA1Hash& partition_key,
    const LogfileScanParams& params,
    const Vector<InetAddr>& hosts,
    LogfileScanResult* result) {
  Vector<String> errors;

  for (const auto& host : hosts) {
    try {
      if(scanRemoteLogfilePartition(
          session,
          table_name,
          partition_key,
          params,
          host,
          result)) {
        return;
      }
    } catch (const StandardException& e) {
      logError(
          "zbase",
          e,
          "LogfileService::scanRemoteLogfilePartition failed");

      errors.emplace_back(e.what());
    }
  }

  if (!errors.empty()) {
    RAISEF(
        kRuntimeError,
        "LogfileService::scanRemoteLogfilePartition failed: $0",
        StringUtil::join(errors, ", "));
  }
}

bool LogfileService::scanRemoteLogfilePartition(
    const AnalyticsSession& session,
    const String& table_name,
    const SHA1Hash& partition_key,
    const LogfileScanParams& params,
    const InetAddr& host,
    LogfileScanResult* result) {
  logDebug(
      "zbase",
      "Scanning remote logfile partition $0/$1/$2 on $3",
      session.customer(),
      table_name,
      partition_key.toString(),
      host.hostAndPort());

  auto url = StringUtil::format(
      "http://$0/api/v1/logfiles/scan_partition?table=$1&partition=$2&limit=$3",
      host.hostAndPort(),
      URI::urlEncode(table_name),
      partition_key.toString(),
      result->capacity());

  auto api_token = auth_->encodeAuthToken(session);

  http::HTTPMessage::HeaderList auth_headers;
  auth_headers.emplace_back(
      "Authorization",
      StringUtil::format("Token $0", api_token));

  http::HTTPClient http_client;
  auto req_body = msg::encode(params);
  auto req = http::HTTPRequest::mkPost(url, *req_body, auth_headers);
  auto res = http_client.executeRequest(req);

  if (res.statusCode() == 404) {
    return false;
  }

  if (res.statusCode() != 200) {
    RAISEF(
        kRuntimeError,
        "received non-200 response: $0", res.body().toString());
  }

  auto body_is = BufferInputStream::fromBuffer(&res.body());
  result->decode(body_is.get());

  return true;
}

void LogfileService::insertLoglines(
    const String& customer,
    const String& logfile_name,
    const Vector<Pair<String, String>>& source_fields,
    InputStream* is) {
  auto logfile_definition = findLogfileDefinition(customer, logfile_name);
  if (logfile_definition.isEmpty()) {
    RAISEF(kNotFoundError, "logfile not found: $0", logfile_name);
  }

  insertLoglines(
      customer,
      logfile_definition.get(),
      source_fields,
      is);
}

void LogfileService::insertLoglines(
    const String& customer,
    const LogfileDefinition& logfile,
    const Vector<Pair<String, String>>& source_fields,
    InputStream* is) {
  String line;

  auto table_name = "logs." + logfile.name();
  auto schema = tsdb_->tableSchema(customer, table_name);
  auto partitioner = tsdb_->tablePartitioner(customer, table_name);
  if (schema.isEmpty() || partitioner.isEmpty()) {
    RAISEF(kNotFoundError, "table not found: $0", table_name);
  }

  msg::DynamicMessage row_base(schema.get());
  for (const auto& f : source_fields) {
    row_base.addField(f.first, f.second);
  }

  RegExp regex(logfile.regex());

  HashMap<size_t, LogfileField> match_fields;
  size_t time_idx = -1;
  String time_format;
  for (const auto& f : logfile.row_fields()) {
    auto match_idx = regex.getNamedCaptureIndex(f.name());
    if (match_idx != size_t(-1)) {
      match_fields.emplace(match_idx, f);
      if (f.name() == "time") {
        time_idx = match_idx;
        time_format = f.format();
      }
    }
  }

  if (time_idx == size_t(-1)) {
    RAISE(kIllegalStateError, "can't import logfile row without time column");
  }

  for (; is->readLine(&line); line.clear()) {
    Vector<Pair<const char*, size_t>> match;
    if (!regex.match(line, &match)) {
      continue;
    }

    Option<UnixTime> time;
    if (time_format.empty()) {
      time = Human::parseTime(
          String(match[time_idx].first, match[time_idx].second));
    } else {
      time = UnixTime::parseString(
          match[time_idx].first,
          match[time_idx].second,
          time_format.c_str());
    }

    if (time.isEmpty()) {
      continue;
    }

    auto row = row_base;
    row.addField("raw", line);

    for (size_t i = 0; i < match.size(); ++i) {
      auto mfield = match_fields.find(i);
      if (mfield == match_fields.end()) {
        continue;
      }

      if (mfield->second.has_format() &&
          mfield->second.type() == "DATETIME") {
        auto t = UnixTime::parseString(
            match[i].first,
            match[i].second,
            mfield->second.format().c_str());

        if (!t.isEmpty()) {
          row.addDateTimeField(mfield->second.id(), t.get());
        }
        continue;
      }

      row.addField(
          mfield->second.id(),
          String(match[i].first, match[i].second));
    }

    Buffer row_buf;
    msg::MessageEncoder::encode(row.data(), *row.schema(), &row_buf);

    auto record_id = Random::singleton()->sha1();
    auto partition_key = partitioner.get()->partitionKeyFor(
        StringUtil::toString(time.get().unixMicros()));

    tsdb_->insertRecord(
        customer,
        table_name,
        partition_key,
        record_id,
        row_buf);
  }
}

Option<LogfileDefinition> LogfileService::findLogfileDefinition(
    const String& customer,
    const String& logfile_name) {
  auto cconf = cdir_->configFor(customer);

  for (const auto& logfile : cconf->config.logfile_import_config().logfiles()) {
    if (logfile.name() == logfile_name) {
      return Some(logfile);
    }
  }

  return None<LogfileDefinition>();
}

RefPtr<msg::MessageSchema> LogfileService::getSchema(
    const LogfileDefinition& cfg) {
  Vector<msg::MessageSchemaField> fields;

  fields.emplace_back(
      msg::MessageSchemaField(
          1,
          "raw",
          msg::FieldType::STRING,
          0,
          false,
          true));

  for (const auto& field : cfg.source_fields()) {
    fields.emplace_back(
        msg::MessageSchemaField(
            field.id(),
            field.name(),
            msg::fieldTypeFromString(field.type()),
            0,
            false,
            true));
  }

  for (const auto& field : cfg.row_fields()) {
    fields.emplace_back(
        msg::MessageSchemaField(
            field.id(),
            field.name(),
            msg::fieldTypeFromString(field.type()),
            0,
            false,
            true));
  }

  return new msg::MessageSchema(cfg.name(), fields);
}

Vector<TableDefinition> LogfileService::getTableDefinitions(
    const CustomerConfig& cfg) {
  Vector<TableDefinition> tbls;

  if (!cfg.has_logfile_import_config()) {
    return tbls;
  }

  for (const auto& logfile : cfg.logfile_import_config().logfiles()) {
    TableDefinition td;
    td.set_customer(cfg.customer());
    td.set_table_name("logs." + logfile.name());
    auto tblcfg = td.mutable_config();
    tblcfg->set_schema(getSchema(logfile)->encode().toString());
    tblcfg->set_partitioner(zbase::TBL_PARTITION_TIMEWINDOW);
    tblcfg->set_storage(zbase::TBL_STORAGE_LOG);

    auto partcfg = tblcfg->mutable_time_window_partitioner_config();
    partcfg->set_partition_size(10 * kMicrosPerMinute);

    tbls.emplace_back(td);
  }

  return tbls;
}

} // namespace zbase
<|endoftext|>


<commit_before>#include "SolverModule.h"

using namespace std;

/**
 *	Constructor for SolverModule.
 *
 *	The solver module holds a WaveSolver object and contains all the accessor methods related to it.
 *	The Solvermodule starts off being stored online in the DatabaseRef singleton class, but is passed
 *	to other controller classes as needed.
*/
SolverModule::SolverModule()
	: mSolver(WaveSolver<double>(X, Y))
{
}

/**
 *	GetSolver()
 *
 *  Gets the WaveSolver
 *
 *	Returns a shared pointer to the current WaveSolver
*/
shared_ptr<WaveSolver<double>> SolverModule::GetSolver() const
{
	return shared_ptr<WaveSolver<double>>();
}

/**
 *	AddRectangle(const int x, const int y, const int width, const int height, const double vel)
 *		x:		The x coordinate of the top left corner of the rectangle to be added
 *		y:		The y coordinate of the top left corner of the rectangle to be added
 *		width:	The width of the rectangle to be added
 *		height:	The height of the rectangle to be added
 *		vel:	The amount of influence the shape will have over its area, 1 having no effect and 0 completely
 *				blocking all movement
 *
 *	Addes a rectangle to the current WaveSovler
*/
const void SolverModule::AddRectangle(const int x, const int y, const int width, const int height, const double vel)
{
	mSolver.addRectangle(x - 1, y - 1, x + width - 1, y + height - 1, vel);
}

/**
 *	AddCircle(const int x, const int y, const int radius, const int vel)
 *		x:		The x coordinate of the center of the circle to be added
 *		y:		The y coordinate of the center of the rectangle to be added
 *		radius:	The radius of the circle to be added
 *		vel:	The amount of influence the shape will have over its area, 1 having no effect and 0 completely
 *				blocking all movement
 *
 *	Addes a rectangle to the current WaveSovler
*/
const void SolverModule::AddCircle(const int x, const int y, const int radius, const double vel)
{
	mSolver.addCircle(x - 1, y - 1, radius, vel);
}

/**
 *	ResetMaterials()
 *
 *	Removes all the shapes from the current WaveSolver but leaves the current simulation running
*/
const void SolverModule::ResetMaterials()
{
	mSolver.resetMaterials();
}

/**
 *	ResetField()
 *
 *	Sets the field back to the initial poisitoin but leaves all shapes
*/
const void SolverModule::ResetField()
{
	mSolver.resetField();
}

/**
 *	GetField()
 *
 *	Returns a raw pointer to field of the current WaveSolver
*/
WaveSolver<double>* SolverModule::GetField()
{
	return &mSolver;
}
<commit_msg>Tweaked adding circle<commit_after>#include "SolverModule.h"

using namespace std;

/**
 *	Constructor for SolverModule.
 *
 *	The solver module holds a WaveSolver object and contains all the accessor methods related to it.
 *	The Solvermodule starts off being stored online in the DatabaseRef singleton class, but is passed
 *	to other controller classes as needed.
*/
SolverModule::SolverModule()
	: mSolver(WaveSolver<double>(X, Y))
{
}

/**
 *	GetSolver()
 *
 *  Gets the WaveSolver
 *
 *	Returns a shared pointer to the current WaveSolver
*/
shared_ptr<WaveSolver<double>> SolverModule::GetSolver() const
{
	return shared_ptr<WaveSolver<double>>();
}

/**
 *	AddRectangle(const int x, const int y, const int width, const int height, const double vel)
 *		x:		The x coordinate of the top left corner of the rectangle to be added
 *		y:		The y coordinate of the top left corner of the rectangle to be added
 *		width:	The width of the rectangle to be added
 *		height:	The height of the rectangle to be added
 *		vel:	The amount of influence the shape will have over its area, 1 having no effect and 0 completely
 *				blocking all movement
 *
 *	Addes a rectangle to the current WaveSovler
*/
const void SolverModule::AddRectangle(const int x, const int y, const int width, const int height, const double vel)
{
	mSolver.addRectangle(x - 1, y - 1, x + width - 1, y + height - 1, vel);
}

/**
 *	AddCircle(const int x, const int y, const int radius, const int vel)
 *		x:		The x coordinate of the center of the circle to be added
 *		y:		The y coordinate of the center of the rectangle to be added
 *		radius:	The radius of the circle to be added
 *		vel:	The amount of influence the shape will have over its area, 1 having no effect and 0 completely
 *				blocking all movement
 *
 *	Addes a rectangle to the current WaveSovler
*/
const void SolverModule::AddCircle(const int x, const int y, const int radius, const double vel)
{
	mSolver.addCircle(x, y, radius - 1, vel);
}

/**
 *	ResetMaterials()
 *
 *	Removes all the shapes from the current WaveSolver but leaves the current simulation running
*/
const void SolverModule::ResetMaterials()
{
	mSolver.resetMaterials();
}

/**
 *	ResetField()
 *
 *	Sets the field back to the initial poisitoin but leaves all shapes
*/
const void SolverModule::ResetField()
{
	mSolver.resetField();
}

/**
 *	GetField()
 *
 *	Returns a raw pointer to field of the current WaveSolver
*/
WaveSolver<double>* SolverModule::GetField()
{
	return &mSolver;
}
<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "modules/congestion_controller/rtp/transport_feedback_adapter.h"

#include <algorithm>

#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/mod_ops.h"

namespace webrtc {
namespace {
void SortPacketFeedbackVector(std::vector<webrtc::PacketFeedback>* input) {
  std::sort(input->begin(), input->end(), PacketFeedbackComparator());
}

PacketResult NetworkPacketFeedbackFromRtpPacketFeedback(
    const webrtc::PacketFeedback& pf) {
  PacketResult feedback;
  if (pf.arrival_time_ms == webrtc::PacketFeedback::kNotReceived)
    feedback.receive_time = Timestamp::PlusInfinity();
  else
    feedback.receive_time = Timestamp::ms(pf.arrival_time_ms);
  if (pf.send_time_ms != webrtc::PacketFeedback::kNoSendTime) {
    feedback.sent_packet = SentPacket();
    feedback.sent_packet->sequence_number = pf.long_sequence_number;
    feedback.sent_packet->send_time = Timestamp::ms(pf.send_time_ms);
    feedback.sent_packet->size = DataSize::bytes(pf.payload_size);
    feedback.sent_packet->pacing_info = pf.pacing_info;
  }
  return feedback;
}

std::vector<PacketResult> PacketResultsFromRtpFeedbackVector(
    const std::vector<PacketFeedback>& feedback_vector) {
  RTC_DCHECK(std::is_sorted(feedback_vector.begin(), feedback_vector.end(),
                            PacketFeedbackComparator()));

  std::vector<PacketResult> packet_feedbacks;
  packet_feedbacks.reserve(feedback_vector.size());
  for (const PacketFeedback& rtp_feedback : feedback_vector) {
    auto feedback = NetworkPacketFeedbackFromRtpPacketFeedback(rtp_feedback);
    packet_feedbacks.push_back(feedback);
  }
  return packet_feedbacks;
}
}  // namespace
const int64_t kNoTimestamp = -1;
const int64_t kSendTimeHistoryWindowMs = 60000;
const int64_t kBaseTimestampScaleFactor =
    rtcp::TransportFeedback::kDeltaScaleFactor * (1 << 8);
const int64_t kBaseTimestampRangeSizeUs = kBaseTimestampScaleFactor * (1 << 24);

TransportFeedbackAdapter::TransportFeedbackAdapter(const Clock* clock)
    : send_time_history_(clock, kSendTimeHistoryWindowMs),
      clock_(clock),
      current_offset_ms_(kNoTimestamp),
      last_timestamp_us_(kNoTimestamp),
      local_net_id_(0),
      remote_net_id_(0) {}

TransportFeedbackAdapter::~TransportFeedbackAdapter() {
  RTC_DCHECK(observers_.empty());
}

void TransportFeedbackAdapter::RegisterPacketFeedbackObserver(
    PacketFeedbackObserver* observer) {
  rtc::CritScope cs(&observers_lock_);
  RTC_DCHECK(observer);
  RTC_DCHECK(std::find(observers_.begin(), observers_.end(), observer) ==
             observers_.end());
  observers_.push_back(observer);
}

void TransportFeedbackAdapter::DeRegisterPacketFeedbackObserver(
    PacketFeedbackObserver* observer) {
  rtc::CritScope cs(&observers_lock_);
  RTC_DCHECK(observer);
  const auto it = std::find(observers_.begin(), observers_.end(), observer);
  RTC_DCHECK(it != observers_.end());
  observers_.erase(it);
}

void TransportFeedbackAdapter::AddPacket(uint32_t ssrc,
                                         uint16_t sequence_number,
                                         size_t length,
                                         const PacedPacketInfo& pacing_info) {
  {
    rtc::CritScope cs(&lock_);
    const int64_t creation_time_ms = clock_->TimeInMilliseconds();
    send_time_history_.AddAndRemoveOld(
        PacketFeedback(creation_time_ms, sequence_number, length, local_net_id_,
                       remote_net_id_, pacing_info));
  }

  {
    rtc::CritScope cs(&observers_lock_);
    for (auto* observer : observers_) {
      observer->OnPacketAdded(ssrc, sequence_number);
    }
  }
}

absl::optional<SentPacket> TransportFeedbackAdapter::ProcessSentPacket(
    const rtc::SentPacket& sent_packet) {
  rtc::CritScope cs(&lock_);
  // TODO(srte): Only use one way to indicate that packet feedback is used.
  if (sent_packet.info.included_in_feedback || sent_packet.packet_id != -1) {
    send_time_history_.OnSentPacket(sent_packet.packet_id,
                                    sent_packet.send_time_ms);
    absl::optional<PacketFeedback> packet =
        send_time_history_.GetPacket(sent_packet.packet_id);
    if (packet) {
      SentPacket msg;
      msg.size = DataSize::bytes(packet->payload_size);
      msg.send_time = Timestamp::ms(packet->send_time_ms);
      msg.sequence_number = packet->long_sequence_number;
      msg.prior_unacked_data = DataSize::bytes(packet->unacknowledged_data);
      msg.data_in_flight =
          send_time_history_.GetOutstandingData(local_net_id_, remote_net_id_);
      return msg;
    }
  } else if (sent_packet.info.included_in_allocation) {
    send_time_history_.AddUntracked(sent_packet.info.packet_size_bytes,
                                    sent_packet.send_time_ms);
  }
  return absl::nullopt;
}

absl::optional<TransportPacketsFeedback>
TransportFeedbackAdapter::ProcessTransportFeedback(
    const rtcp::TransportFeedback& feedback) {
  int64_t feedback_time_ms = clock_->TimeInMilliseconds();
  DataSize prior_in_flight = GetOutstandingData();
  OnTransportFeedback(feedback);
  std::vector<PacketFeedback> feedback_vector = last_packet_feedback_vector_;
  if (feedback_vector.empty())
    return absl::nullopt;

  SortPacketFeedbackVector(&feedback_vector);
  TransportPacketsFeedback msg;
  msg.packet_feedbacks = PacketResultsFromRtpFeedbackVector(feedback_vector);
  msg.feedback_time = Timestamp::ms(feedback_time_ms);
  msg.prior_in_flight = prior_in_flight;
  msg.data_in_flight = GetOutstandingData();
  return msg;
}

void TransportFeedbackAdapter::SetNetworkIds(uint16_t local_id,
                                             uint16_t remote_id) {
  rtc::CritScope cs(&lock_);
  local_net_id_ = local_id;
  remote_net_id_ = remote_id;
}

DataSize TransportFeedbackAdapter::GetOutstandingData() const {
  rtc::CritScope cs(&lock_);
  return send_time_history_.GetOutstandingData(local_net_id_, remote_net_id_);
}

std::vector<PacketFeedback> TransportFeedbackAdapter::GetPacketFeedbackVector(
    const rtcp::TransportFeedback& feedback) {
  int64_t timestamp_us = feedback.GetBaseTimeUs();
  int64_t now_ms = clock_->TimeInMilliseconds();
  // Add timestamp deltas to a local time base selected on first packet arrival.
  // This won't be the true time base, but makes it easier to manually inspect
  // time stamps.
  if (last_timestamp_us_ == kNoTimestamp) {
    current_offset_ms_ = now_ms;
  } else {
    int64_t delta = timestamp_us - last_timestamp_us_;

    // Detect and compensate for wrap-arounds in base time.
    if (std::abs(delta - kBaseTimestampRangeSizeUs) < std::abs(delta)) {
      delta -= kBaseTimestampRangeSizeUs;  // Wrap backwards.
    } else if (std::abs(delta + kBaseTimestampRangeSizeUs) < std::abs(delta)) {
      delta += kBaseTimestampRangeSizeUs;  // Wrap forwards.
    }

    current_offset_ms_ += delta / 1000;
  }
  last_timestamp_us_ = timestamp_us;

  std::vector<PacketFeedback> packet_feedback_vector;
  if (feedback.GetPacketStatusCount() == 0) {
    RTC_LOG(LS_INFO) << "Empty transport feedback packet received.";
    return packet_feedback_vector;
  }
  packet_feedback_vector.reserve(feedback.GetPacketStatusCount());
  {
    rtc::CritScope cs(&lock_);
    size_t failed_lookups = 0;
    int64_t offset_us = 0;
    int64_t timestamp_ms = 0;
    uint16_t seq_num = feedback.GetBaseSequence();
    for (const auto& packet : feedback.GetReceivedPackets()) {
      // Insert into the vector those unreceived packets which precede this
      // iteration's received packet.
      for (; seq_num != packet.sequence_number(); ++seq_num) {
        PacketFeedback packet_feedback(PacketFeedback::kNotReceived, seq_num);
        // Note: Element not removed from history because it might be reported
        // as received by another feedback.
        if (!send_time_history_.GetFeedback(&packet_feedback, false))
          ++failed_lookups;
        if (packet_feedback.local_net_id == local_net_id_ &&
            packet_feedback.remote_net_id == remote_net_id_) {
          packet_feedback_vector.push_back(packet_feedback);
        }
      }

      // Handle this iteration's received packet.
      offset_us += packet.delta_us();
      timestamp_ms = current_offset_ms_ + (offset_us / 1000);
      PacketFeedback packet_feedback(timestamp_ms, packet.sequence_number());
      if (!send_time_history_.GetFeedback(&packet_feedback, true))
        ++failed_lookups;
      if (packet_feedback.local_net_id == local_net_id_ &&
          packet_feedback.remote_net_id == remote_net_id_) {
        packet_feedback_vector.push_back(packet_feedback);
      }

      ++seq_num;
    }

    if (failed_lookups > 0) {
      RTC_LOG(LS_WARNING) << "Failed to lookup send time for " << failed_lookups
                          << " packet" << (failed_lookups > 1 ? "s" : "")
                          << ". Send time history too small?";
    }
  }
  return packet_feedback_vector;
}

void TransportFeedbackAdapter::OnTransportFeedback(
    const rtcp::TransportFeedback& feedback) {
  last_packet_feedback_vector_ = GetPacketFeedbackVector(feedback);
  {
    rtc::CritScope cs(&observers_lock_);
    for (auto* observer : observers_) {
      observer->OnPacketFeedbackVector(last_packet_feedback_vector_);
    }
  }
}

std::vector<PacketFeedback>
TransportFeedbackAdapter::GetTransportFeedbackVector() const {
  return last_packet_feedback_vector_;
}
}  // namespace webrtc
<commit_msg>Routing unacknowledged data in TransportFeedbackAdapter.<commit_after>/*
 *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "modules/congestion_controller/rtp/transport_feedback_adapter.h"

#include <algorithm>

#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/mod_ops.h"

namespace webrtc {
namespace {
void SortPacketFeedbackVector(std::vector<webrtc::PacketFeedback>* input) {
  std::sort(input->begin(), input->end(), PacketFeedbackComparator());
}

PacketResult NetworkPacketFeedbackFromRtpPacketFeedback(
    const webrtc::PacketFeedback& pf) {
  PacketResult feedback;
  if (pf.arrival_time_ms == webrtc::PacketFeedback::kNotReceived)
    feedback.receive_time = Timestamp::PlusInfinity();
  else
    feedback.receive_time = Timestamp::ms(pf.arrival_time_ms);
  if (pf.send_time_ms != webrtc::PacketFeedback::kNoSendTime) {
    feedback.sent_packet = SentPacket();
    feedback.sent_packet->sequence_number = pf.long_sequence_number;
    feedback.sent_packet->send_time = Timestamp::ms(pf.send_time_ms);
    feedback.sent_packet->size = DataSize::bytes(pf.payload_size);
    feedback.sent_packet->pacing_info = pf.pacing_info;
    feedback.sent_packet->prior_unacked_data =
        DataSize::bytes(pf.unacknowledged_data);
  }
  return feedback;
}

std::vector<PacketResult> PacketResultsFromRtpFeedbackVector(
    const std::vector<PacketFeedback>& feedback_vector) {
  RTC_DCHECK(std::is_sorted(feedback_vector.begin(), feedback_vector.end(),
                            PacketFeedbackComparator()));

  std::vector<PacketResult> packet_feedbacks;
  packet_feedbacks.reserve(feedback_vector.size());
  for (const PacketFeedback& rtp_feedback : feedback_vector) {
    auto feedback = NetworkPacketFeedbackFromRtpPacketFeedback(rtp_feedback);
    packet_feedbacks.push_back(feedback);
  }
  return packet_feedbacks;
}
}  // namespace
const int64_t kNoTimestamp = -1;
const int64_t kSendTimeHistoryWindowMs = 60000;
const int64_t kBaseTimestampScaleFactor =
    rtcp::TransportFeedback::kDeltaScaleFactor * (1 << 8);
const int64_t kBaseTimestampRangeSizeUs = kBaseTimestampScaleFactor * (1 << 24);

TransportFeedbackAdapter::TransportFeedbackAdapter(const Clock* clock)
    : send_time_history_(clock, kSendTimeHistoryWindowMs),
      clock_(clock),
      current_offset_ms_(kNoTimestamp),
      last_timestamp_us_(kNoTimestamp),
      local_net_id_(0),
      remote_net_id_(0) {}

TransportFeedbackAdapter::~TransportFeedbackAdapter() {
  RTC_DCHECK(observers_.empty());
}

void TransportFeedbackAdapter::RegisterPacketFeedbackObserver(
    PacketFeedbackObserver* observer) {
  rtc::CritScope cs(&observers_lock_);
  RTC_DCHECK(observer);
  RTC_DCHECK(std::find(observers_.begin(), observers_.end(), observer) ==
             observers_.end());
  observers_.push_back(observer);
}

void TransportFeedbackAdapter::DeRegisterPacketFeedbackObserver(
    PacketFeedbackObserver* observer) {
  rtc::CritScope cs(&observers_lock_);
  RTC_DCHECK(observer);
  const auto it = std::find(observers_.begin(), observers_.end(), observer);
  RTC_DCHECK(it != observers_.end());
  observers_.erase(it);
}

void TransportFeedbackAdapter::AddPacket(uint32_t ssrc,
                                         uint16_t sequence_number,
                                         size_t length,
                                         const PacedPacketInfo& pacing_info) {
  {
    rtc::CritScope cs(&lock_);
    const int64_t creation_time_ms = clock_->TimeInMilliseconds();
    send_time_history_.AddAndRemoveOld(
        PacketFeedback(creation_time_ms, sequence_number, length, local_net_id_,
                       remote_net_id_, pacing_info));
  }

  {
    rtc::CritScope cs(&observers_lock_);
    for (auto* observer : observers_) {
      observer->OnPacketAdded(ssrc, sequence_number);
    }
  }
}

absl::optional<SentPacket> TransportFeedbackAdapter::ProcessSentPacket(
    const rtc::SentPacket& sent_packet) {
  rtc::CritScope cs(&lock_);
  // TODO(srte): Only use one way to indicate that packet feedback is used.
  if (sent_packet.info.included_in_feedback || sent_packet.packet_id != -1) {
    send_time_history_.OnSentPacket(sent_packet.packet_id,
                                    sent_packet.send_time_ms);
    absl::optional<PacketFeedback> packet =
        send_time_history_.GetPacket(sent_packet.packet_id);
    if (packet) {
      SentPacket msg;
      msg.size = DataSize::bytes(packet->payload_size);
      msg.send_time = Timestamp::ms(packet->send_time_ms);
      msg.sequence_number = packet->long_sequence_number;
      msg.prior_unacked_data = DataSize::bytes(packet->unacknowledged_data);
      msg.data_in_flight =
          send_time_history_.GetOutstandingData(local_net_id_, remote_net_id_);
      return msg;
    }
  } else if (sent_packet.info.included_in_allocation) {
    send_time_history_.AddUntracked(sent_packet.info.packet_size_bytes,
                                    sent_packet.send_time_ms);
  }
  return absl::nullopt;
}

absl::optional<TransportPacketsFeedback>
TransportFeedbackAdapter::ProcessTransportFeedback(
    const rtcp::TransportFeedback& feedback) {
  int64_t feedback_time_ms = clock_->TimeInMilliseconds();
  DataSize prior_in_flight = GetOutstandingData();
  OnTransportFeedback(feedback);
  std::vector<PacketFeedback> feedback_vector = last_packet_feedback_vector_;
  if (feedback_vector.empty())
    return absl::nullopt;

  SortPacketFeedbackVector(&feedback_vector);
  TransportPacketsFeedback msg;
  msg.packet_feedbacks = PacketResultsFromRtpFeedbackVector(feedback_vector);
  msg.feedback_time = Timestamp::ms(feedback_time_ms);
  msg.prior_in_flight = prior_in_flight;
  msg.data_in_flight = GetOutstandingData();
  return msg;
}

void TransportFeedbackAdapter::SetNetworkIds(uint16_t local_id,
                                             uint16_t remote_id) {
  rtc::CritScope cs(&lock_);
  local_net_id_ = local_id;
  remote_net_id_ = remote_id;
}

DataSize TransportFeedbackAdapter::GetOutstandingData() const {
  rtc::CritScope cs(&lock_);
  return send_time_history_.GetOutstandingData(local_net_id_, remote_net_id_);
}

std::vector<PacketFeedback> TransportFeedbackAdapter::GetPacketFeedbackVector(
    const rtcp::TransportFeedback& feedback) {
  int64_t timestamp_us = feedback.GetBaseTimeUs();
  int64_t now_ms = clock_->TimeInMilliseconds();
  // Add timestamp deltas to a local time base selected on first packet arrival.
  // This won't be the true time base, but makes it easier to manually inspect
  // time stamps.
  if (last_timestamp_us_ == kNoTimestamp) {
    current_offset_ms_ = now_ms;
  } else {
    int64_t delta = timestamp_us - last_timestamp_us_;

    // Detect and compensate for wrap-arounds in base time.
    if (std::abs(delta - kBaseTimestampRangeSizeUs) < std::abs(delta)) {
      delta -= kBaseTimestampRangeSizeUs;  // Wrap backwards.
    } else if (std::abs(delta + kBaseTimestampRangeSizeUs) < std::abs(delta)) {
      delta += kBaseTimestampRangeSizeUs;  // Wrap forwards.
    }

    current_offset_ms_ += delta / 1000;
  }
  last_timestamp_us_ = timestamp_us;

  std::vector<PacketFeedback> packet_feedback_vector;
  if (feedback.GetPacketStatusCount() == 0) {
    RTC_LOG(LS_INFO) << "Empty transport feedback packet received.";
    return packet_feedback_vector;
  }
  packet_feedback_vector.reserve(feedback.GetPacketStatusCount());
  {
    rtc::CritScope cs(&lock_);
    size_t failed_lookups = 0;
    int64_t offset_us = 0;
    int64_t timestamp_ms = 0;
    uint16_t seq_num = feedback.GetBaseSequence();
    for (const auto& packet : feedback.GetReceivedPackets()) {
      // Insert into the vector those unreceived packets which precede this
      // iteration's received packet.
      for (; seq_num != packet.sequence_number(); ++seq_num) {
        PacketFeedback packet_feedback(PacketFeedback::kNotReceived, seq_num);
        // Note: Element not removed from history because it might be reported
        // as received by another feedback.
        if (!send_time_history_.GetFeedback(&packet_feedback, false))
          ++failed_lookups;
        if (packet_feedback.local_net_id == local_net_id_ &&
            packet_feedback.remote_net_id == remote_net_id_) {
          packet_feedback_vector.push_back(packet_feedback);
        }
      }

      // Handle this iteration's received packet.
      offset_us += packet.delta_us();
      timestamp_ms = current_offset_ms_ + (offset_us / 1000);
      PacketFeedback packet_feedback(timestamp_ms, packet.sequence_number());
      if (!send_time_history_.GetFeedback(&packet_feedback, true))
        ++failed_lookups;
      if (packet_feedback.local_net_id == local_net_id_ &&
          packet_feedback.remote_net_id == remote_net_id_) {
        packet_feedback_vector.push_back(packet_feedback);
      }

      ++seq_num;
    }

    if (failed_lookups > 0) {
      RTC_LOG(LS_WARNING) << "Failed to lookup send time for " << failed_lookups
                          << " packet" << (failed_lookups > 1 ? "s" : "")
                          << ". Send time history too small?";
    }
  }
  return packet_feedback_vector;
}

void TransportFeedbackAdapter::OnTransportFeedback(
    const rtcp::TransportFeedback& feedback) {
  last_packet_feedback_vector_ = GetPacketFeedbackVector(feedback);
  {
    rtc::CritScope cs(&observers_lock_);
    for (auto* observer : observers_) {
      observer->OnPacketFeedbackVector(last_packet_feedback_vector_);
    }
  }
}

std::vector<PacketFeedback>
TransportFeedbackAdapter::GetTransportFeedbackVector() const {
  return last_packet_feedback_vector_;
}
}  // namespace webrtc
<|endoftext|>