Skip to content

2. DVS event stream counter

Alexandre Marcireau edited this page Jun 27, 2018 · 20 revisions

goal: develop an application to count the number of events per second emitted by an Event Stream file

duration: 30 minutes

prerequisites:

result files: basics/2_dvs_event_stream_counter


steps

  1. functions limits as event handlers
  2. first solution: callable classes
  3. second solution: lambda functions

functions limits as event handlers

The application presented in this tutorial must count the number of events received per second. Therefore, a variable that will be incremented every time an event is produced, then displayed and reset every second is required. This variable must be preserved over successive events, hence over successive function calls. Moreover, another variable is required to hold the last display-and-reset timestamp, to determine whether a new value must be displayed.

As seen previously, functions can be used to describe the instructions to execute in response to an event. However, functions cannot hold a state. This tutorial introduces two ways to create an event handler which holds a state.

first solution: callable classes

Classes generate objects with member variables that can represent a state. Moreover, C++ provides a mechanism to make an object callable: the operator () can be used on the object as if it were a function.

Duplicate the project created in the previous tutorial, and name the copied directory dvs_event_stream_counter. Rename dvs_event_stream_counter/source/dvs_event_stream_printer.cpp to dvs_event_stream_counter/source/dvs_event_stream_counter.cpp, and change the solution and project names in dvs_event_stream_counter/premake4.lua.

Replace the function handle_event with a class called handle_event in the dvs_event_stream_counter/source/dvs_event_stream_counter.cpp file. To make the class callable, add the member operator() to the class. Write the following content to dvs_event_stream_counter/source/dvs_event_stream_counter.cpp:

#include "../third_party/sepia/source/sepia.hpp"
#include <iostream>

/// handle_event is an event handler for an observable.
class handle_event {
    public:
    handle_event() = default;

    /// operator() handles an event.
    void operator()(sepia::dvs_event dvs_event) {
        ++_count;
        if (dvs_event.t >= _previous_t + 100000) {
            std::cout << (_count * 10) << " events / second" << std::endl;
            _previous_t = dvs_event.t;
            _count = 0;
        }
    }

    private:
    std::size_t _count;
    uint64_t _previous_t;
};

The private member _count and _previous_t start with _ by convention. _count stores the number of events handled since the last print. _previous_t stores the timestamp of the last event that triggered a print. Once every 0.1 s (100000 µs), _count is used to estimate the number of events received per second.

Since HandleEvent is a class, you need to call its constructor to create an object. Since this object will be used only once, it is possible to avoid creating an extra variable and directly pass the result of handle_event() to sepia::join_observable<sepia::type::dvs>. Write the following content to dvs_event_stream_counter/source/dvs_event_stream_counter.cpp:

#include "../third_party/sepia/source/sepia.hpp"
#include <iostream>

/// filename points to the Event Stream file to read.
const auto filename = sepia::join(
    {sepia::dirname(SEPIA_DIRNAME), "third_party", "sepia", "third_party", "event_stream", "examples", "dvs.es"});

/// handle_event is an event handler for an observable.
class handle_event {
    public:
    handle_event() = default;

    /// operator() handles an event.
    void operator()(sepia::dvs_event dvs_event) {
        ++_count;
        if (dvs_event.t >= _previous_t + 100000) {
            std::cout << (_count * 10) << " events / second" << std::endl;
            _previous_t = dvs_event.t;
            _count = 0;
        }
    }

    private:
    std::size_t _count;
    uint64_t _previous_t;
};

int main() {
    sepia::join_observable<sepia::type::dvs>(
        sepia::filename_to_ifstream(filename), 
        handle_event()); // this is not a call to a function, 
                         // but the creation of an unnamed object of the class 'handle_event'
    return 0;
}

You can now compile and execute the application by running from the dvs_event_stream_counter directory:

premake4 gmake
cd build
make
release/dvs_event_stream_counter

Windows users must run premake4 vs2010 instead, and open the generated solution with Visual Studio.

second solution: lambda functions

C++11 (new standard released in 2011) introduced a new mechanism called lambda functions. A lambda function is an anonymous function (it does not have a name) stored in a variable that can be called like a regular function. Moreover, it can hold a state. It is very close to the callable class introduced in the previous section. However, it is easier to declare.

A lambda function can be declared inside another function. It has the following syntax:

auto event_handler = [/*variables to capture*/](/*function arguments*/) mutable -> /*returnType*/ {
    // do something with the function arguments
};

The lambda function is strongly typed, however its type cannot be explicitly specified. The auto keyword is replaced during compilation by the lambda function's type. The variables to capture are existing variables that must be held by the lambda function (they behave as would member variables in a callable class). Write the following content to dvs_event_stream_counter/source/dvs_event_stream_counter.cpp:

#include "../third_party/sepia/source/sepia.hpp"
#include <iostream>

/// filename points to the Event Stream file to read.
const auto filename = sepia::join(
    {sepia::dirname(SEPIA_DIRNAME), "third_party", "sepia", "third_party", "event_stream", "examples", "dvs.es"});

int main() {
    std::size_t count = 0;
    uint64_t previous_t = 0;
    auto handle_event = [count, previous_t](sepia::dvs_event dvs_event) mutable {
        ++count;
        if (dvs_event.t >= previous_t + 100000) {
            std::cout << (count * 10) << " events / second" << std::endl;
            previous_t = dvs_event.t;
            count = 0;
        }
    };

    sepia::join_observable<sepia::type::dvs>(sepia::filename_to_ifstream(filename), handle_event);    
    return 0;
}

Since the variable handle_event is used only once, you can directly pass the result of the lambda function declaration to sepia::join_observable<sepia::type::dvs>. Write the following content to dvs_event_stream_counter/source/dvs_event_stream_counter.cpp:

#include "../third_party/sepia/source/sepia.hpp"
#include <iostream>

/// filename points to the Event Stream file to read.
const auto filename = sepia::join(
    {sepia::dirname(SEPIA_DIRNAME), "third_party", "sepia", "third_party", "event_stream", "examples", "dvs.es"});

int main() {
    std::size_t count = 0;
    uint64_t previous_t = 0;
    sepia::join_observable<sepia::type::dvs>(
        sepia::filename_to_ifstream(filename),
        [&](sepia::dvs_event dvs_event) {
            ++count;
            if (dvs_event.t >= previous_t + 100000) {
                std::cout << (count * 10) << " events / second" << std::endl;
                previous_t = dvs_event.t;
                count = 0;
            }
        });
    return 0;
}

Unlike the previous example, the variables count and previous_t are captured by reference here (using &). Therefore, the mutable keyword is not required.

You can now compile and execute the application. You are ready for the flow averager tutorial.