bool ReceivedPacketProcessor::process() {

    if (_packets.size() == 0) {
        _waitingOnPacketsMutex.lock();
        _hasPackets.wait(&_waitingOnPacketsMutex);
        _waitingOnPacketsMutex.unlock();
    }
    while (_packets.size() > 0) {
        lock(); // lock to make sure nothing changes on us
        NetworkPacket& packet = _packets.front(); // get the oldest packet
        NetworkPacket temporary = packet; // make a copy of the packet in case the vector is resized on us
        _packets.erase(_packets.begin()); // remove the oldest packet
        _nodePacketCounts[temporary.getNode()->getUUID()]--;
        unlock(); // let others add to the packets
        processPacket(temporary.getNode(), temporary.getByteArray()); // process our temporary copy
    }
    return isStillRunning();  // keep running till they terminate us
}
Beispiel #2
0
// We may be called more frequently than we get packets or need to send packets, we may also get called less frequently.
//
// If we're called more often then out target PPS then we will space out our actual sends to be a single packet for multiple
// calls to process. Those calls to proces in which we do not need to send a packet to keep up with our target PPS we will
// just track our call rate (in order to predict our sends per call) but we won't actually send any packets.
//
// When we are called less frequently than we have packets to send, we will send enough packets per call to keep up with our
// target PPS.
//
// We also keep a running total of packets sent over multiple calls to process() so that we can adjust up or down for
// possible rounding error that would occur if we only considered whole integer packet counts per call to process
bool PacketSender::nonThreadedProcess() {
    quint64 now = usecTimestampNow();

    if (_lastProcessCallTime == 0) {
        _lastProcessCallTime = now - _usecsPerProcessCallHint;
    }

    const quint64 MINIMUM_POSSIBLE_CALL_TIME = 10; // in usecs
    const quint64 USECS_PER_SECOND = 1000 * 1000;
    const float ZERO_RESET_CALLS_PER_SECOND = 1; // used in guard against divide by zero

    // keep track of our process call times, so we have a reliable account of how often our caller calls us
    quint64 elapsedSinceLastCall = now - _lastProcessCallTime;
    _lastProcessCallTime = now;
    _averageProcessCallTime.updateAverage(elapsedSinceLastCall);

    float averageCallTime = 0;
    const int TRUST_AVERAGE_AFTER = AVERAGE_CALL_TIME_SAMPLES * 2;
    if (_usecsPerProcessCallHint == 0 || _averageProcessCallTime.getSampleCount() > TRUST_AVERAGE_AFTER) {
        averageCallTime = _averageProcessCallTime.getAverage();
    } else {
        averageCallTime = _usecsPerProcessCallHint;
    }

    if (_packets.size() == 0) {
        // in non-threaded mode, if there's nothing to do, just return, keep running till they terminate us
        return isStillRunning();
    }

    // This only happens once, the first time we get this far... so we can use it as an accurate initialization
    // point for these important timing variables
    if (_lastPPSCheck == 0) {
        _lastPPSCheck = now;
        // pretend like our lifetime began once call cycle for now, this makes our lifetime PPS start out most accurately
        _started = now - (quint64)averageCallTime;
    }


    float averagePacketsPerCall = 0;  // might be less than 1, if our caller calls us more frequently than the target PPS
    int packetsSentThisCall = 0;
    int packetsToSendThisCall = 0;

    // Since we're in non-threaded mode, we need to determine how many packets to send per call to process
    // based on how often we get called... We do this by keeping a running average of our call times, and we determine
    // how many packets to send per call

    // We assume you can't possibly call us less than MINIMUM_POSSIBLE_CALL_TIME apart
    if (averageCallTime <= 0) {
        averageCallTime = MINIMUM_POSSIBLE_CALL_TIME;
    }

    // we can determine how many packets we need to send per call to achieve our desired
    // packets per second send rate.
    float callsPerSecond = USECS_PER_SECOND / averageCallTime;

    // theoretically we could get called less than 1 time per second... but since we're using floats, it really shouldn't be
    // possible to get 0 calls per second, but we will guard agains that here, just in case.
    if (callsPerSecond == 0) {
        callsPerSecond = ZERO_RESET_CALLS_PER_SECOND;
    }

    // This is the average number of packets per call...
    averagePacketsPerCall = _packetsPerSecond / callsPerSecond;
    packetsToSendThisCall = averagePacketsPerCall;

    // if we get called more than 1 per second, we want to mostly divide the packets evenly across the calls...
    // but we want to track the remainder and make sure over the course of a second, we are sending the target PPS
    // e.g.
    //     200pps called 60 times per second...
    //     200/60 = 3.333... so really...
    //     each call we should send 3
    //     every 3rd call we should send 4...
    //     3,3,4,3,3,4...3,3,4 = 200...

    // if we get called less than 1 per second, then we want to send more than our PPS each time...
    // e.g.
    //     200pps called ever 1332.5ms
    //     200 / (1000/1332.5) = 200/(0.7505) = 266.5 packets per call
    //     so...
    //        every other call we should send 266 packets
    //        then on the next call we should send 267 packets

    // So no mater whether or not we're getting called more or less than once per second, we still need to do some bookkeeping
    // to make sure we send a few extra packets to even out our flow rate.
    quint64 elapsedSinceLastCheck = now - _lastPPSCheck;

    // we might want to tun this in the future and only check after a certain number of call intervals. for now we check
    // each time and adjust accordingly
    const float CALL_INTERVALS_TO_CHECK = 1;
    const float MIN_CALL_INTERVALS_PER_RESET = 5;

    // we will reset our check PPS and time each second (callsPerSecond) or at least 5 calls (if we get called less frequently
    // than 5 times per second) This gives us sufficient smoothing in our packet adjustments
    float callIntervalsPerReset = std::max(callsPerSecond, MIN_CALL_INTERVALS_PER_RESET);

    if  (elapsedSinceLastCheck > (averageCallTime * CALL_INTERVALS_TO_CHECK)) {
        float ppsOverCheckInterval = (float)_packetsOverCheckInterval;
        float ppsExpectedForCheckInterval = (float)_packetsPerSecond * ((float)elapsedSinceLastCheck / (float)USECS_PER_SECOND);

        if (ppsOverCheckInterval < ppsExpectedForCheckInterval) {
            int adjust = ppsExpectedForCheckInterval - ppsOverCheckInterval;
            packetsToSendThisCall += adjust;
        } else if (ppsOverCheckInterval > ppsExpectedForCheckInterval) {
            int adjust = ppsOverCheckInterval - ppsExpectedForCheckInterval;
            packetsToSendThisCall -= adjust;
        }

        // now, do we want to reset the check interval? don't want to completely reset, because we would still have
        // a rounding error. instead, we check to see that we've passed the reset interval (which is much larger than
        // the check interval), and on those reset intervals we take the second half average and keep that for the next
        // interval window...
        if (elapsedSinceLastCheck > (averageCallTime * callIntervalsPerReset)) {
            // Keep average packets and time for "second half" of check interval
            _lastPPSCheck += (elapsedSinceLastCheck / 2);
            _packetsOverCheckInterval = (_packetsOverCheckInterval / 2);

            elapsedSinceLastCheck = now - _lastPPSCheck;
        }
    }

    int packetsLeft = _packets.size();

    // Now that we know how many packets to send this call to process, just send them.
    while ((packetsSentThisCall < packetsToSendThisCall) && (packetsLeft > 0)) {
        lock();
        NetworkPacket& packet = _packets.front();
        NetworkPacket temporary = packet; // make a copy
        _packets.erase(_packets.begin());
        packetsLeft = _packets.size();
        unlock();

        // send the packet through the NodeList...
        NodeList::getInstance()->writeDatagram(temporary.getByteArray(), temporary.getDestinationNode());
        packetsSentThisCall++;
        _packetsOverCheckInterval++;
        _totalPacketsSent++;
        _totalBytesSent += temporary.getByteArray().size();
        
        emit packetSent(temporary.getByteArray().size());
        
        _lastSendTime = now;
    }
    return isStillRunning();
}
Beispiel #3
0
NetworkPacket::NetworkPacket(const NetworkPacket& packet) {
    copyContents(packet.getNode(), packet.getByteArray());
}