<commit_before>#include <im_str/im_str.hpp>
#include <iostream>
#include <string>
#include <thread>

#include <catch2/catch.hpp>

using namespace std::literals;

void requireZero( std::string_view view )
{
	REQUIRE( view.data()[view.size()] == '\0' );
}

template<class Container>
constexpr bool has_standard_container_typedefs()
{
	using T = Container;
	[[maybe_unused]] typename T::traits_type            traits {};
	[[maybe_unused]] typename T::value_type             v {};
	[[maybe_unused]] typename T::pointer                p {};
	[[maybe_unused]] typename T::const_pointer          cp {};
	[[maybe_unused]] typename T::reference              r  = v;
	[[maybe_unused]] typename T::const_reference        cr = v;
	[[maybe_unused]] typename T::const_iterator         cit {};
	[[maybe_unused]] typename T::iterator               it {};
	[[maybe_unused]] typename T::reverse_iterator       rit {};
	[[maybe_unused]] typename T::const_reverse_iterator crit {};
	[[maybe_unused]] typename T::size_type              s {};
	[[maybe_unused]] typename T::difference_type        d {};
	return true;
}

void static_checks()
{
	static_assert( has_standard_container_typedefs<mba::im_str>() );
	static_assert( has_standard_container_typedefs<mba::im_zstr>() );
}

TEST_CASE( "Construction from literal", "[im_str]" )
{
	{
		mba::im_str str1 = "Hello World";
		REQUIRE( str1 == "Hello World" );

		mba::im_str str2 {"Hello World"};
		REQUIRE( str2 == "Hello World" );

		mba::im_str str3 = {"Hello World"};
		REQUIRE( str3 == "Hello World" );
	}

	{
		mba::im_zstr zstr1 = "Hello World";
		REQUIRE( zstr1 == "Hello World" );

		mba::im_zstr zstr2 {"Hello World"};
		REQUIRE( zstr2 == "Hello World" );

		mba::im_zstr zstr3 = {"Hello World"};
		REQUIRE( zstr3 == "Hello World" );
	}
}

TEST_CASE( "Construction empty", "[im_str]" )
{
	{
		mba::im_str str1 = "";
		mba::im_str str2 {};
		REQUIRE( str1 == str2 );
	}
	{
		mba::im_zstr zstr1 = "";
		mba::im_zstr zstr2 {};
		REQUIRE( zstr1 == zstr2 );
	}
}

TEST_CASE( "Construction from std::string", "[im_str]" )
{
	{
		mba::im_str str1( "Hello World" );
		REQUIRE( str1 == "Hello World" );

		mba::im_str str2 {"Hello World"s};
		REQUIRE( str2 == "Hello World" );

		auto stdstr = "Hello World"s;

		mba::im_str str3 {stdstr};
		REQUIRE( str3 == "Hello World" );
	}
	{
		mba::im_zstr zstr1( "Hello World" );
		REQUIRE( zstr1 == "Hello World" );

		mba::im_zstr zstr2 {"Hello World"s};
		REQUIRE( zstr2 == "Hello World" );

		auto stdstr = "Hello World"s;

		mba::im_str zstr3 {stdstr};
		REQUIRE( zstr3 == "Hello World" );
	}
}

TEST_CASE( "Construction from temporary std::string", "[im_str]" )
{
	{
		mba::im_str cs = [] {
			auto        stdstr = "Hello World"s;
			mba::im_str cs {stdstr};
			stdstr[0] = 'M'; // modify original string to make sure we really have an independent copy
			REQUIRE( cs != stdstr );
			return cs;
		}();
		REQUIRE( cs == "Hello World" );
	}
	{
		mba::im_zstr cs = [] {
			auto         stdstr = "Hello World"s;
			mba::im_zstr cs {stdstr};
			stdstr[0] = 'M'; // modify original string to make sure we really have an independent copy
			REQUIRE( cs != stdstr );
			return cs;
		}();
		REQUIRE( cs == "Hello World" );
	}
}

TEST_CASE( "Construction from im_str", "[im_zstr]" )
{
	std::string cppstring = "Hello World, how are you?";
	{
		const mba::im_str ims_z {cppstring};

		auto imzs1 = ims_z.create_zstr();
		static_assert( std::is_same_v<decltype( imzs1 ), mba::im_zstr> );
		CHECK( ims_z == imzs1 );

		// construction from temporary
		auto imzs2 = mba::im_str( ims_z ).create_zstr();
		static_assert( std::is_same_v<decltype( imzs2 ), mba::im_zstr> );
		CHECK( ims_z == imzs2 );
	}

	// construction from an ims_nz that isn't alredy zero terminated
	{
		mba::im_str ims_nz = mba::im_str( cppstring ).substr( 0, 3 );

		auto imzs3 = ims_nz.create_zstr();
		static_assert( std::is_same_v<decltype( imzs3 ), mba::im_zstr> );
		CHECK( ims_nz == imzs3 );
		CHECK( mba::im_str( imzs3 ).is_zero_terminated() );

		// construction from temporary
		auto imzs4 = mba::im_str( ims_nz ).create_zstr();
		static_assert( std::is_same_v<decltype( imzs4 ), mba::im_zstr> );
		CHECK( ims_nz == imzs4 );
		CHECK( mba::im_str( imzs4 ).is_zero_terminated() );
	}
}

TEST_CASE( "Copy", "[im_str]" )
{
	{
		mba::im_str str1;
		{
			// no heap allocation
			mba::im_str tcs = "Hello World";
			str1            = tcs;
		}
		REQUIRE( str1 == "Hello World" );

		mba::im_str str2;
		{
			// heap allocated
			mba::im_str tcs {"Hello World"s};
			str2 = tcs;
		}
		REQUIRE( str2 == "Hello World" );
	}
	{
		mba::im_zstr str1;
		{
			// no heap allocation
			mba::im_zstr tcs = "Hello World";
			str1             = tcs;
		}
		REQUIRE( str1 == "Hello World" );

		mba::im_zstr str2;
		{
			// heap allocated
			mba::im_zstr tcs {"Hello World"s};
			str2 = tcs;
		}
		REQUIRE( str2 == "Hello World" );
	}
}

TEST_CASE( "concat", "[im_str]" )
{
	mba::im_str cs       = "How are you?";
	auto        combined = concat( "Hello", " World! "s, cs );
	REQUIRE( combined == "Hello World! How are you?" );
	REQUIRE( combined.is_zero_terminated() );
	requireZero( combined );
}

TEST_CASE( "comparison", "[im_str]" )
{
	mba::im_str str1 = "Hello1";
	mba::im_str str2 = "Hello2";

	CHECK( str1 < str2 );
	CHECK( str1 > "Hello" );
	CHECK( str2 < std::string( "Hello2o" ) );
}

TEST_CASE( "thread" )
{
	constexpr int iterations = 100'000;
	using namespace std::literals;
	const std::string    cpps1 = "Good";
	const std::string    cpps2 = "Bad";
	mba::im_str          s1 {"Hello"s};
	mba::im_str          s2 {"World!"s};
	std::atomic_uint64_t total = 0;

	std::atomic_int total_cpps1_fail_cnt = 0;
	std::atomic_int total_cpps2_fail_cnt = 0;
	std::atomic_int total_s1_fail_cnt    = 0;
	std::atomic_int total_s2_fail_cnt    = 0;

	auto f = [&, s1, s2] {
		std::size_t sum            = 0;
		int         cpps1_fail_cnt = 0;
		int         cpps2_fail_cnt = 0;
		int         s1_fail_cnt    = 0;
		int         s2_fail_cnt    = 0;
		for( int i = 0; i < iterations; i++ ) {
			{
				mba::im_str cs {cpps1};
				sum += cs.size();
				cpps1_fail_cnt += cs[0] != 'G';
			}
			{
				mba::im_str cs {cpps2};
				sum += cs.size();
				cpps2_fail_cnt += cs[0] != 'B';
			}
			{
				auto s = s1;
				sum += s.size();
				s1_fail_cnt += s[0] != 'H';
				s = s2;
				s2_fail_cnt += s[0] != 'W';
				sum += s.size();
			}
		}
		total += sum;
		total_cpps1_fail_cnt += cpps1_fail_cnt;
		total_cpps2_fail_cnt += cpps2_fail_cnt;
		total_s1_fail_cnt += s1_fail_cnt;
		total_s2_fail_cnt += s2_fail_cnt;
	};
	std::thread th1( f );
	std::thread th2( f );
	th1.join();
	th2.join();
	REQUIRE( total == ( s1.size() + s2.size() + cpps1.size() + cpps2.size() ) * 2 * iterations );
	REQUIRE( total_cpps1_fail_cnt == 0 );
	REQUIRE( total_cpps2_fail_cnt == 0 );
	REQUIRE( total_s1_fail_cnt == 0 );
	REQUIRE( total_s2_fail_cnt == 0 );
}
<commit_msg>[tests] Unit test the examples<commit_after>#include <im_str/im_str.hpp>
#include <iostream>
#include <string>
#include <thread>

#include <catch2/catch.hpp>

using namespace std::literals;

void requireZero( std::string_view view )
{
	REQUIRE( view.data()[view.size()] == '\0' );
}

template<class Container>
constexpr bool has_standard_container_typedefs()
{
	using T = Container;
	[[maybe_unused]] typename T::traits_type            traits {};
	[[maybe_unused]] typename T::value_type             v {};
	[[maybe_unused]] typename T::pointer                p {};
	[[maybe_unused]] typename T::const_pointer          cp {};
	[[maybe_unused]] typename T::reference              r  = v;
	[[maybe_unused]] typename T::const_reference        cr = v;
	[[maybe_unused]] typename T::const_iterator         cit {};
	[[maybe_unused]] typename T::iterator               it {};
	[[maybe_unused]] typename T::reverse_iterator       rit {};
	[[maybe_unused]] typename T::const_reverse_iterator crit {};
	[[maybe_unused]] typename T::size_type              s {};
	[[maybe_unused]] typename T::difference_type        d {};
	return true;
}

void static_checks()
{
	static_assert( has_standard_container_typedefs<mba::im_str>() );
	static_assert( has_standard_container_typedefs<mba::im_zstr>() );
}

TEST_CASE( "Construction from literal", "[im_str]" )
{
	{
		mba::im_str str1 = "Hello World";
		REQUIRE( str1 == "Hello World" );

		mba::im_str str2 {"Hello World"};
		REQUIRE( str2 == "Hello World" );

		mba::im_str str3 = {"Hello World"};
		REQUIRE( str3 == "Hello World" );
	}

	{
		mba::im_zstr zstr1 = "Hello World";
		REQUIRE( zstr1 == "Hello World" );

		mba::im_zstr zstr2 {"Hello World"};
		REQUIRE( zstr2 == "Hello World" );

		mba::im_zstr zstr3 = {"Hello World"};
		REQUIRE( zstr3 == "Hello World" );
	}
}

TEST_CASE( "Construction empty", "[im_str]" )
{
	{
		mba::im_str str1 = "";
		mba::im_str str2 {};
		REQUIRE( str1 == str2 );
	}
	{
		mba::im_zstr zstr1 = "";
		mba::im_zstr zstr2 {};
		REQUIRE( zstr1 == zstr2 );
	}
}

TEST_CASE( "Construction from std::string", "[im_str]" )
{
	{
		mba::im_str str1( "Hello World" );
		REQUIRE( str1 == "Hello World" );

		mba::im_str str2 {"Hello World"s};
		REQUIRE( str2 == "Hello World" );

		auto stdstr = "Hello World"s;

		mba::im_str str3 {stdstr};
		REQUIRE( str3 == "Hello World" );
	}
	{
		mba::im_zstr zstr1( "Hello World" );
		REQUIRE( zstr1 == "Hello World" );

		mba::im_zstr zstr2 {"Hello World"s};
		REQUIRE( zstr2 == "Hello World" );

		auto stdstr = "Hello World"s;

		mba::im_str zstr3 {stdstr};
		REQUIRE( zstr3 == "Hello World" );
	}
}

TEST_CASE( "Construction from temporary std::string", "[im_str]" )
{
	{
		mba::im_str cs = [] {
			auto        stdstr = "Hello World"s;
			mba::im_str cs {stdstr};
			stdstr[0] = 'M'; // modify original string to make sure we really have an independent copy
			REQUIRE( cs != stdstr );
			return cs;
		}();
		REQUIRE( cs == "Hello World" );
	}
	{
		mba::im_zstr cs = [] {
			auto         stdstr = "Hello World"s;
			mba::im_zstr cs {stdstr};
			stdstr[0] = 'M'; // modify original string to make sure we really have an independent copy
			REQUIRE( cs != stdstr );
			return cs;
		}();
		REQUIRE( cs == "Hello World" );
	}
}

TEST_CASE( "Construction from im_str", "[im_zstr]" )
{
	std::string cppstring = "Hello World, how are you?";
	{
		const mba::im_str ims_z {cppstring};

		auto imzs1 = ims_z.create_zstr();
		static_assert( std::is_same_v<decltype( imzs1 ), mba::im_zstr> );
		CHECK( ims_z == imzs1 );

		// construction from temporary
		auto imzs2 = mba::im_str( ims_z ).create_zstr();
		static_assert( std::is_same_v<decltype( imzs2 ), mba::im_zstr> );
		CHECK( ims_z == imzs2 );
	}

	// construction from an ims_nz that isn't alredy zero terminated
	{
		mba::im_str ims_nz = mba::im_str( cppstring ).substr( 0, 3 );

		auto imzs3 = ims_nz.create_zstr();
		static_assert( std::is_same_v<decltype( imzs3 ), mba::im_zstr> );
		CHECK( ims_nz == imzs3 );
		CHECK( mba::im_str( imzs3 ).is_zero_terminated() );

		// construction from temporary
		auto imzs4 = mba::im_str( ims_nz ).create_zstr();
		static_assert( std::is_same_v<decltype( imzs4 ), mba::im_zstr> );
		CHECK( ims_nz == imzs4 );
		CHECK( mba::im_str( imzs4 ).is_zero_terminated() );
	}
}

TEST_CASE( "Copy", "[im_str]" )
{
	{
		mba::im_str str1;
		{
			// no heap allocation
			mba::im_str tcs = "Hello World";
			str1            = tcs;
		}
		REQUIRE( str1 == "Hello World" );

		mba::im_str str2;
		{
			// heap allocated
			mba::im_str tcs {"Hello World"s};
			str2 = tcs;
		}
		REQUIRE( str2 == "Hello World" );
	}
	{
		mba::im_zstr str1;
		{
			// no heap allocation
			mba::im_zstr tcs = "Hello World";
			str1             = tcs;
		}
		REQUIRE( str1 == "Hello World" );

		mba::im_zstr str2;
		{
			// heap allocated
			mba::im_zstr tcs {"Hello World"s};
			str2 = tcs;
		}
		REQUIRE( str2 == "Hello World" );
	}
}

TEST_CASE( "concat", "[im_str]" )
{
	mba::im_str cs       = "How are you?";
	auto        combined = concat( "Hello", " World! "s, cs );
	REQUIRE( combined == "Hello World! How are you?" );
	REQUIRE( combined.is_zero_terminated() );
	requireZero( combined );
}

TEST_CASE( "comparison", "[im_str]" )
{
	mba::im_str str1 = "Hello1";
	mba::im_str str2 = "Hello2";

	CHECK( str1 < str2 );
	CHECK( str1 > "Hello" );
	CHECK( str2 < std::string( "Hello2o" ) );
}

TEST_CASE( "thread" )
{
	constexpr int iterations = 100'000;
	using namespace std::literals;
	const std::string    cpps1 = "Good";
	const std::string    cpps2 = "Bad";
	mba::im_str          s1 {"Hello"s};
	mba::im_str          s2 {"World!"s};
	std::atomic_uint64_t total = 0;

	std::atomic_int total_cpps1_fail_cnt = 0;
	std::atomic_int total_cpps2_fail_cnt = 0;
	std::atomic_int total_s1_fail_cnt    = 0;
	std::atomic_int total_s2_fail_cnt    = 0;

	auto f = [&, s1, s2] {
		std::size_t sum            = 0;
		int         cpps1_fail_cnt = 0;
		int         cpps2_fail_cnt = 0;
		int         s1_fail_cnt    = 0;
		int         s2_fail_cnt    = 0;
		for( int i = 0; i < iterations; i++ ) {
			{
				mba::im_str cs {cpps1};
				sum += cs.size();
				cpps1_fail_cnt += cs[0] != 'G';
			}
			{
				mba::im_str cs {cpps2};
				sum += cs.size();
				cpps2_fail_cnt += cs[0] != 'B';
			}
			{
				auto s = s1;
				sum += s.size();
				s1_fail_cnt += s[0] != 'H';
				s = s2;
				s2_fail_cnt += s[0] != 'W';
				sum += s.size();
			}
		}
		total += sum;
		total_cpps1_fail_cnt += cpps1_fail_cnt;
		total_cpps2_fail_cnt += cpps2_fail_cnt;
		total_s1_fail_cnt += s1_fail_cnt;
		total_s2_fail_cnt += s2_fail_cnt;
	};
	std::thread th1( f );
	std::thread th2( f );
	th1.join();
	th2.join();
	REQUIRE( total == ( s1.size() + s2.size() + cpps1.size() + cpps2.size() ) * 2 * iterations );
	REQUIRE( total_cpps1_fail_cnt == 0 );
	REQUIRE( total_cpps2_fail_cnt == 0 );
	REQUIRE( total_s1_fail_cnt == 0 );
	REQUIRE( total_s2_fail_cnt == 0 );
}

void c_func( const char* ) {}

TEST_CASE( "Examples", "[im_str]" )
{
	{
		using namespace mba;
		im_str name = "John";
		assert( name == "John" );
		assert( name.size() == 4 );
		std::cout << name; // Will print "John";

		im_str cpy = name;
		name       = im_str( "Jane Doe" );
		assert( cpy == "John" );

		auto [first, second] = name.split_on_first( ' ' );

		name = im_str {};
		cpy  = im_str {};

		assert( first == "Jane" );
		assert( second == "Doe" );
	}
	{
		std::string name = "Mike";

		mba::im_str  is           = mba::im_str( name );                   // This allocates
		mba::im_str full_greeting = mba::concat( "Hello, ", name, "!\n" ); // This will also allocate (once)

		std::cout << full_greeting; // Prints "Hello, Mike!", followed by a newline
	}
	{
		using namespace mba;
		im_str full = "Hello, World!";
		assert( full.is_zero_terminated() );

		im_str sub = full.substr( 0, 3 );
		assert( sub.is_zero_terminated() == false );

		im_zstr subz = sub.create_zstr();    // This will allocate
		assert( subz.is_zero_terminated() ); // This will always be true

		im_zstr fullz = std::move( full ).create_zstr(); // This  will not allocate
		assert( full.empty() );
		c_func( fullz.c_str() );
	}
}
<|endoftext|>


<commit_before>#include "EntriesModel.hpp"

#include <climits>
#include <type_traits>

#include <QtCore/QByteArray>
#include <QtCore/QHash>
#include <QtCore/QVector>

#include "Entry.hpp"
#include "Feed.hpp"

namespace feedling {

EntriesModel::EntriesModel(QObject *parent)
  : QAbstractListModel (parent)
{}

EntriesModel::~EntriesModel() = default;

QVariant EntriesModel::data(const QModelIndex &index, int role) const
{
    auto data = QVariant();
    if (index.isValid()) {
        const auto *entry = static_cast<const Entry *>(index.internalPointer());
        switch (role) {
        case Roles::CONTENT:
            data.setValue<QString>(entry->content());
            break;
        case Roles::DATETIME:
            data.setValue<QDateTime>(entry->dateTime());
            break;
        case Qt::DisplayRole:
        case Roles::TITLE:
            data.setValue<QString>(entry->title());
            break;
        }
    }
    return data;
}

QModelIndex EntriesModel::index(int row, int column, const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    Q_UNUSED(column);

    auto index = QModelIndex();

    if ((row >= 0) && (row < m_feed->size())) {
        index = createIndex(row, column, m_feed->getEntry(row).get());
    }

    return index;
}

QHash<int, QByteArray> EntriesModel::roleNames() const
{
    QHash<int, QByteArray> roles;

    roles[Roles::CONTENT] = "content";
    roles[Roles::DATETIME] = "dateTime";
    roles[Roles::TITLE] = "title";

    return roles;
}

int EntriesModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);

    return m_feed->size();
}

void EntriesModel::setFeed(const std::shared_ptr<Feed> &feed)
{
    m_feed = feed;
    emit dataChanged(index(0, 0, QModelIndex{}),
                     index(feed->size() - 1, 0, QModelIndex{}),
                     QVector<int>{Qt::DisplayRole, Roles::CONTENT, Roles::DATETIME, Roles::TITLE});
}

std::shared_ptr<Entry> EntriesModel::getEntry(const QModelIndex &idx) const
{
    if (idx.isValid()) {
        return static_cast<Entry *>(idx.internalPointer())->shared_from_this();
    }
    return std::shared_ptr<Entry>{};
}

}  // feedling
<commit_msg>Fix ``nullptr`` dereferencation if no feed was set on ``EntriesModel``<commit_after>#include "EntriesModel.hpp"

#include <climits>
#include <type_traits>

#include <QtCore/QByteArray>
#include <QtCore/QHash>
#include <QtCore/QVector>

#include "Entry.hpp"
#include "Feed.hpp"

namespace feedling {

EntriesModel::EntriesModel(QObject *parent)
  : QAbstractListModel (parent)
{}

EntriesModel::~EntriesModel() = default;

QVariant EntriesModel::data(const QModelIndex &index, int role) const
{
    auto data = QVariant();
    if (index.isValid()) {
        const auto *entry = static_cast<const Entry *>(index.internalPointer());
        switch (role) {
        case Roles::CONTENT:
            data.setValue<QString>(entry->content());
            break;
        case Roles::DATETIME:
            data.setValue<QDateTime>(entry->dateTime());
            break;
        case Qt::DisplayRole:
        case Roles::TITLE:
            data.setValue<QString>(entry->title());
            break;
        }
    }
    return data;
}

QModelIndex EntriesModel::index(int row, int column, const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    Q_UNUSED(column);

    auto index = QModelIndex();

    if ((row >= 0) && (row < m_feed->size())) {
        index = createIndex(row, column, m_feed->getEntry(row).get());
    }

    return index;
}

QHash<int, QByteArray> EntriesModel::roleNames() const
{
    QHash<int, QByteArray> roles;

    roles[Roles::CONTENT] = "content";
    roles[Roles::DATETIME] = "dateTime";
    roles[Roles::TITLE] = "title";

    return roles;
}

int EntriesModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);

    if (m_feed) {
        return m_feed->size();
    }
    return 0;
}

void EntriesModel::setFeed(const std::shared_ptr<Feed> &feed)
{
    m_feed = feed;
    emit dataChanged(index(0, 0, QModelIndex{}),
                     index(feed->size() - 1, 0, QModelIndex{}),
                     QVector<int>{Qt::DisplayRole, Roles::CONTENT, Roles::DATETIME, Roles::TITLE});
}

std::shared_ptr<Entry> EntriesModel::getEntry(const QModelIndex &idx) const
{
    if (idx.isValid()) {
        return static_cast<Entry *>(idx.internalPointer())->shared_from_this();
    }
    return std::shared_ptr<Entry>{};
}

}  // feedling
<|endoftext|>


<commit_before>#include <znc/main.h>
#include <znc/User.h>
#include <znc/IRCNetwork.h>
#include <znc/Modules.h>
#include <znc/Nick.h>
#include <znc/Chan.h>
#include <znc/IRCSock.h>
#include <znc/version.h>

#include <limits>

#include "twitchtmi.h"
#include "jload.h"

#if (VERSION_MAJOR < 1) || (VERSION_MAJOR == 1 && VERSION_MINOR < 7)
#error This module needs at least ZNC 1.7.0 or later.
#endif

TwitchTMI::~TwitchTMI()
{

}

bool TwitchTMI::OnLoad(const CString& sArgsi, CString& sMessage)
{
	OnBoot();

	if(GetNetwork())
	{
		for(CChan *ch: GetNetwork()->GetChans())
		{
			ch->SetTopic(CString());

			CString chname = ch->GetName().substr(1);
			CThreadPool::Get().addJob(new TwitchTMIJob(this, chname));
		}
	}

	if(GetArgs().Token(0) != "FrankerZ")
		lastFrankerZ = std::numeric_limits<decltype(lastFrankerZ)>::max();

	PutIRC("CAP REQ :twitch.tv/membership");
	PutIRC("CAP REQ :twitch.tv/commands");
	PutIRC("CAP REQ :twitch.tv/tags");

	return true;
}

bool TwitchTMI::OnBoot()
{
	initCurl();

	timer = new TwitchTMIUpdateTimer(this);
	AddTimer(timer);

	return true;
}

void TwitchTMI::OnIRCConnected()
{
	PutIRC("CAP REQ :twitch.tv/membership");
	PutIRC("CAP REQ :twitch.tv/commands");
	PutIRC("CAP REQ :twitch.tv/tags");
}

CModule::EModRet TwitchTMI::OnUserRaw(CString &sLine)
{
	if(sLine.Left(5).Equals("WHO #"))
		return CModule::HALT;

	if(sLine.Left(5).Equals("AWAY "))
		return CModule::HALT;

	if(sLine.Left(12).Equals("TWITCHCLIENT"))
		return CModule::HALT;

	if(sLine.Left(9).Equals("JTVCLIENT"))
		return CModule::HALT;

	return CModule::CONTINUE;
}

CModule::EModRet TwitchTMI::OnRawMessage(CMessage &msg)
{
	if(msg.GetCommand().Equals("HOSTTARGET"))
	{
		return CModule::HALT;
	}
	else if(msg.GetCommand().Equals("CLEARCHAT"))
	{
		msg.SetCommand("NOTICE");
		if(msg.GetParam(1) != "")
		{
			msg.SetParam(1, msg.GetParam(1) + " was timed out.");
		}
		else
		{
			msg.SetParam(1, "Chat was cleared by a moderator.");
		}
	}
	else if(msg.GetCommand().Equals("USERSTATE"))
	{
		return CModule::HALT;
	}
	else if(msg.GetCommand().Equals("ROOMSTATE"))
	{
		return CModule::HALT;
	}
	else if(msg.GetCommand().Equals("RECONNECT"))
	{
		return CModule::HALT;
	}
	else if(msg.GetCommand().Equals("GLOBALUSERSTATE"))
	{
		return CModule::HALT;
	}
	else if(msg.GetCommand().Equals("WHISPER"))
	{
		msg.SetCommand("PRIVMSG");
	}

	CString realNick = msg.GetTag("display-name").Trim_n();
	if(realNick != "")
		msg.GetNick().SetNick(realNick);

	return CModule::CONTINUE;
}

CModule::EModRet TwitchTMI::OnUserJoin(CString& sChannel, CString& sKey)
{
	CString chname = sChannel.substr(1);
	CThreadPool::Get().addJob(new TwitchTMIJob(this, chname));

	return CModule::CONTINUE;
}

CModule::EModRet TwitchTMI::OnPrivMessage(CTextMessage &Message)
{
	if(Message.GetNick().GetNick().Equals("jtv"))
		return CModule::HALT;

	return CModule::CONTINUE;
}

void TwitchTMI::PutUserChanMessage(CChan *chan, const CString &from, const CString &msg)
{
	std::stringstream ss;
	ss << ":" << from << " PRIVMSG " << chan->GetName() << " :";
	CString s = ss.str();

	PutUser(s + msg);

	if(!chan->AutoClearChanBuffer() || !GetNetwork()->IsUserOnline() || chan->IsDetached())
		chan->AddBuffer(s + "{text}", msg);
}

CModule::EModRet TwitchTMI::OnChanMessage(CTextMessage &Message)
{
	if(Message.GetNick().GetNick().Equals("jtv"))
		return CModule::HALT;

	if(Message.GetText() == "FrankerZ" && std::time(nullptr) - lastFrankerZ > 10)
	{
		std::stringstream ss;
		CString mynick = GetNetwork()->GetIRCNick().GetNickMask();
		CChan *chan = Message.GetChan();

		ss << "PRIVMSG " << chan->GetName() << " :FrankerZ";
		PutIRC(ss.str());

		CThreadPool::Get().addJob(new GenericJob([]() {}, [this, chan, mynick]()
		{
			PutUserChanMessage(chan, mynick, "FrankerZ");
		}));

		lastFrankerZ = std::time(nullptr);
	}

	return CModule::CONTINUE;
}

bool TwitchTMI::OnServerCapAvailable(const CString &sCap)
{
	if(sCap == "twitch.tv/membership")
		return true;
	else if(sCap == "twitch.tv/tags")
		return true;
	else if(sCap == "twitch.tv/commands")
		return true;

	return false;
}

CModule::EModRet TwitchTMI::OnUserTextMessage(CTextMessage &msg)
{
	if(msg.GetTarget().Left(1).Equals("#"))
		return CModule::CONTINUE;

	msg.SetText(msg.GetText().insert(0, " ").insert(0, msg.GetTarget()).insert(0, "/w "));
	msg.SetTarget("#jtv");

	return CModule::CONTINUE;
}

TwitchTMIUpdateTimer::TwitchTMIUpdateTimer(TwitchTMI *tmod)
	:CTimer(tmod, 30, 0, "TwitchTMIUpdateTimer", "Downloads Twitch information")
	,mod(tmod)
{
}

void TwitchTMIUpdateTimer::RunJob()
{
	if(!mod->GetNetwork())
		return;

	for(CChan *chan: mod->GetNetwork()->GetChans())
	{
		CString chname = chan->GetName().substr(1);
		CThreadPool::Get().addJob(new TwitchTMIJob(mod, chname));
	}
}

void TwitchTMIJob::runThread()
{
	std::stringstream ss, ss2;
	ss << "https://api.twitch.tv/kraken/channels/" << channel;
	ss2 << "https://api.twitch.tv/kraken/streams/" << channel;

	CString url = ss.str();
	CString url2 = ss2.str();

	Json::Value root = getJsonFromUrl(url.c_str(), "Accept: application/vnd.twitchtv.v3+json");
	Json::Value root2 = getJsonFromUrl(url2.c_str(), "Accept: application/vnd.twitchtv.v3+json");

	if(!root.isNull())
	{
		Json::Value &titleVal = root["status"];
		title = CString();

		if(!titleVal.isString())
			titleVal = root["title"];

		if(titleVal.isString())
		{
			title = titleVal.asString();
			title.Trim();
		}
	}

	live = false;

	if(!root2.isNull())
	{
		Json::Value &streamVal = root2["stream"];

		if(!streamVal.isNull())
			live = true;
	}
}

void TwitchTMIJob::runMain()
{
	if(title.empty())
		return;

	CChan *chan = mod->GetNetwork()->FindChan(CString("#") + channel);

	if(!chan)
		return;

	if(chan->GetTopic() != title)
	{
		chan->SetTopic(title);
		chan->SetTopicOwner("jtv");
		chan->SetTopicDate((unsigned long)time(nullptr));

		std::stringstream ss;
		ss << ":jtv TOPIC #" << channel << " :" << title;

		mod->PutUser(ss.str());
	}

	auto it = mod->liveChannels.find(channel);
	if(it != mod->liveChannels.end())
	{
		if(!live)
		{
			mod->liveChannels.erase(it);
			mod->PutUserChanMessage(chan, "jtv", ">>> Channel just went offline! <<<");
		}
	}
	else
	{
		if(live)
		{
			mod->liveChannels.insert(channel);
			mod->PutUserChanMessage(chan, "jtv", ">>> Channel just went live! <<<");
		}
	}
}

template<> void TModInfo<TwitchTMI>(CModInfo &info)
{
	info.SetWikiPage("Twitch");
	info.SetHasArgs(true);
}

NETWORKMODULEDEFS(TwitchTMI, "Twitch IRC helper module")
<commit_msg>Add USERNOTICE handler<commit_after>#include <znc/main.h>
#include <znc/User.h>
#include <znc/IRCNetwork.h>
#include <znc/Modules.h>
#include <znc/Nick.h>
#include <znc/Chan.h>
#include <znc/IRCSock.h>
#include <znc/version.h>

#include <limits>

#include "twitchtmi.h"
#include "jload.h"

#if (VERSION_MAJOR < 1) || (VERSION_MAJOR == 1 && VERSION_MINOR < 7)
#error This module needs at least ZNC 1.7.0 or later.
#endif

TwitchTMI::~TwitchTMI()
{

}

bool TwitchTMI::OnLoad(const CString& sArgsi, CString& sMessage)
{
	OnBoot();

	if(GetNetwork())
	{
		for(CChan *ch: GetNetwork()->GetChans())
		{
			ch->SetTopic(CString());

			CString chname = ch->GetName().substr(1);
			CThreadPool::Get().addJob(new TwitchTMIJob(this, chname));
		}
	}

	if(GetArgs().Token(0) != "FrankerZ")
		lastFrankerZ = std::numeric_limits<decltype(lastFrankerZ)>::max();

	PutIRC("CAP REQ :twitch.tv/membership");
	PutIRC("CAP REQ :twitch.tv/commands");
	PutIRC("CAP REQ :twitch.tv/tags");

	return true;
}

bool TwitchTMI::OnBoot()
{
	initCurl();

	timer = new TwitchTMIUpdateTimer(this);
	AddTimer(timer);

	return true;
}

void TwitchTMI::OnIRCConnected()
{
	PutIRC("CAP REQ :twitch.tv/membership");
	PutIRC("CAP REQ :twitch.tv/commands");
	PutIRC("CAP REQ :twitch.tv/tags");
}

CModule::EModRet TwitchTMI::OnUserRaw(CString &sLine)
{
	if(sLine.Left(5).Equals("WHO #"))
		return CModule::HALT;

	if(sLine.Left(5).Equals("AWAY "))
		return CModule::HALT;

	if(sLine.Left(12).Equals("TWITCHCLIENT"))
		return CModule::HALT;

	if(sLine.Left(9).Equals("JTVCLIENT"))
		return CModule::HALT;

	return CModule::CONTINUE;
}

CModule::EModRet TwitchTMI::OnRawMessage(CMessage &msg)
{
	if(msg.GetCommand().Equals("HOSTTARGET"))
	{
		return CModule::HALT;
	}
	else if(msg.GetCommand().Equals("CLEARCHAT"))
	{
		msg.SetCommand("NOTICE");
		if(msg.GetParam(1) != "")
		{
			msg.SetParam(1, msg.GetParam(1) + " was timed out.");
		}
		else
		{
			msg.SetParam(1, "Chat was cleared by a moderator.");
		}
	}
	else if(msg.GetCommand().Equals("USERSTATE"))
	{
		return CModule::HALT;
	}
	else if(msg.GetCommand().Equals("ROOMSTATE"))
	{
		return CModule::HALT;
	}
	else if(msg.GetCommand().Equals("RECONNECT"))
	{
		return CModule::HALT;
	}
	else if(msg.GetCommand().Equals("GLOBALUSERSTATE"))
	{
		return CModule::HALT;
	}
	else if(msg.GetCommand().Equals("WHISPER"))
	{
		msg.SetCommand("PRIVMSG");
	}
	else if(msg.GetCommand().Equals("USERNOTICE"))
	{
		//TODO: Translate Tags to useful message
		msg.SetCommand("PRIVMSG");
		msg.SetParam(1, "<<<RESUB>>> " + msg.GetParam(1));
	}

	CString realNick = msg.GetTag("display-name").Trim_n();
	if(realNick != "")
		msg.GetNick().SetNick(realNick);

	return CModule::CONTINUE;
}

CModule::EModRet TwitchTMI::OnUserJoin(CString& sChannel, CString& sKey)
{
	CString chname = sChannel.substr(1);
	CThreadPool::Get().addJob(new TwitchTMIJob(this, chname));

	return CModule::CONTINUE;
}

CModule::EModRet TwitchTMI::OnPrivMessage(CTextMessage &Message)
{
	if(Message.GetNick().GetNick().Equals("jtv"))
		return CModule::HALT;

	return CModule::CONTINUE;
}

void TwitchTMI::PutUserChanMessage(CChan *chan, const CString &from, const CString &msg)
{
	std::stringstream ss;
	ss << ":" << from << " PRIVMSG " << chan->GetName() << " :";
	CString s = ss.str();

	PutUser(s + msg);

	if(!chan->AutoClearChanBuffer() || !GetNetwork()->IsUserOnline() || chan->IsDetached())
		chan->AddBuffer(s + "{text}", msg);
}

CModule::EModRet TwitchTMI::OnChanMessage(CTextMessage &Message)
{
	if(Message.GetNick().GetNick().Equals("jtv"))
		return CModule::HALT;

	if(Message.GetText() == "FrankerZ" && std::time(nullptr) - lastFrankerZ > 10)
	{
		std::stringstream ss;
		CString mynick = GetNetwork()->GetIRCNick().GetNickMask();
		CChan *chan = Message.GetChan();

		ss << "PRIVMSG " << chan->GetName() << " :FrankerZ";
		PutIRC(ss.str());

		CThreadPool::Get().addJob(new GenericJob([]() {}, [this, chan, mynick]()
		{
			PutUserChanMessage(chan, mynick, "FrankerZ");
		}));

		lastFrankerZ = std::time(nullptr);
	}

	return CModule::CONTINUE;
}

bool TwitchTMI::OnServerCapAvailable(const CString &sCap)
{
	if(sCap == "twitch.tv/membership")
		return true;
	else if(sCap == "twitch.tv/tags")
		return true;
	else if(sCap == "twitch.tv/commands")
		return true;

	return false;
}

CModule::EModRet TwitchTMI::OnUserTextMessage(CTextMessage &msg)
{
	if(msg.GetTarget().Left(1).Equals("#"))
		return CModule::CONTINUE;

	msg.SetText(msg.GetText().insert(0, " ").insert(0, msg.GetTarget()).insert(0, "/w "));
	msg.SetTarget("#jtv");

	return CModule::CONTINUE;
}

TwitchTMIUpdateTimer::TwitchTMIUpdateTimer(TwitchTMI *tmod)
	:CTimer(tmod, 30, 0, "TwitchTMIUpdateTimer", "Downloads Twitch information")
	,mod(tmod)
{
}

void TwitchTMIUpdateTimer::RunJob()
{
	if(!mod->GetNetwork())
		return;

	for(CChan *chan: mod->GetNetwork()->GetChans())
	{
		CString chname = chan->GetName().substr(1);
		CThreadPool::Get().addJob(new TwitchTMIJob(mod, chname));
	}
}

void TwitchTMIJob::runThread()
{
	std::stringstream ss, ss2;
	ss << "https://api.twitch.tv/kraken/channels/" << channel;
	ss2 << "https://api.twitch.tv/kraken/streams/" << channel;

	CString url = ss.str();
	CString url2 = ss2.str();

	Json::Value root = getJsonFromUrl(url.c_str(), "Accept: application/vnd.twitchtv.v3+json");
	Json::Value root2 = getJsonFromUrl(url2.c_str(), "Accept: application/vnd.twitchtv.v3+json");

	if(!root.isNull())
	{
		Json::Value &titleVal = root["status"];
		title = CString();

		if(!titleVal.isString())
			titleVal = root["title"];

		if(titleVal.isString())
		{
			title = titleVal.asString();
			title.Trim();
		}
	}

	live = false;

	if(!root2.isNull())
	{
		Json::Value &streamVal = root2["stream"];

		if(!streamVal.isNull())
			live = true;
	}
}

void TwitchTMIJob::runMain()
{
	if(title.empty())
		return;

	CChan *chan = mod->GetNetwork()->FindChan(CString("#") + channel);

	if(!chan)
		return;

	if(chan->GetTopic() != title)
	{
		chan->SetTopic(title);
		chan->SetTopicOwner("jtv");
		chan->SetTopicDate((unsigned long)time(nullptr));

		std::stringstream ss;
		ss << ":jtv TOPIC #" << channel << " :" << title;

		mod->PutUser(ss.str());
	}

	auto it = mod->liveChannels.find(channel);
	if(it != mod->liveChannels.end())
	{
		if(!live)
		{
			mod->liveChannels.erase(it);
			mod->PutUserChanMessage(chan, "jtv", ">>> Channel just went offline! <<<");
		}
	}
	else
	{
		if(live)
		{
			mod->liveChannels.insert(channel);
			mod->PutUserChanMessage(chan, "jtv", ">>> Channel just went live! <<<");
		}
	}
}

template<> void TModInfo<TwitchTMI>(CModInfo &info)
{
	info.SetWikiPage("Twitch");
	info.SetHasArgs(true);
}

NETWORKMODULEDEFS(TwitchTMI, "Twitch IRC helper module")
<|endoftext|>


<commit_before>// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
#include <vespa/fnet/frt/frt.h>
#include <mutex>
#include <condition_variable>

struct RPC : public FRT_Invokable
{
  void GetInfo(FRT_RPCRequest *req)
  {
    req->GetReturn()->AddString("fastos X current");
    req->GetReturn()->AddString(FNET_Info::GetFNETVersion());
    const char *endian_str = "UNKNOWN";
    if (FNET_Info::GetEndian() == FNET_Info::ENDIAN_LITTLE)
      endian_str = "LITTLE";
    if (FNET_Info::GetEndian() == FNET_Info::ENDIAN_BIG)
      endian_str = "BIG";
    req->GetReturn()->AddString(endian_str);
    req->GetReturn()->AddInt32(FD_SETSIZE);
    req->GetReturn()->AddInt32(sizeof(FRT_RPCRequest));
  }

  void Init(FRT_Supervisor *s)
  {
    FRT_ReflectionBuilder rb(s);
    //-------------------------------------------------------------------
    rb.DefineMethod("getInfo", "", "sssii", true,
                    FRT_METHOD(RPC::GetInfo), this);
    // FastOS version
    // FNET version
    // endian
    // FD_SETSIZE
    // req object size
    //-------------------------------------------------------------------
  }
};

TEST("info") {
    RPC rpc;
    FRT_Supervisor orb;
    char spec[64];
    rpc.Init(&orb);
    ASSERT_TRUE(orb.Listen("tcp/0"));
    sprintf(spec, "tcp/localhost:%d", orb.GetListenPort());
    ASSERT_TRUE(orb.Start());

    FRT_Target     *target      = orb.GetTarget(spec);
    FRT_RPCRequest *local_info  = orb.AllocRPCRequest();
    FRT_RPCRequest *remote_info = orb.AllocRPCRequest();

    rpc.GetInfo(local_info);
    remote_info->SetMethodName("getInfo");
    target->InvokeSync(remote_info, 10.0);
    EXPECT_FALSE(remote_info->IsError());

    FRT_Values &l = *local_info->GetReturn();
 // FRT_Values &r = *remote_info->GetReturn();

    fprintf(stderr, "FastOS Version: %s\n", l[0]._string._str);
    fprintf(stderr, "FNET Version: %s\n", l[1]._string._str);
    fprintf(stderr, "Endian: %s\n", l[2]._string._str);
    fprintf(stderr, "FD_SETSIZE: %d\n", l[3]._intval32);
    fprintf(stderr, "sizeof(FRT_RPCRequest): %d\n", l[4]._intval32);

    target->SubRef();
    local_info->SubRef();
    remote_info->SubRef();
    orb.ShutDown(true);
};

TEST("size of important objects")
{
    EXPECT_EQUAL(184u, sizeof(FNET_IOComponent));
    EXPECT_EQUAL(32u, sizeof(FNET_Channel));
    EXPECT_EQUAL(40u, sizeof(FNET_PacketQueue_NoLock));
    EXPECT_EQUAL(536u, sizeof(FNET_Connection));
    EXPECT_EQUAL(96u, sizeof(FNET_Cond));
    EXPECT_EQUAL(56u, sizeof(FNET_DataBuffer));
    EXPECT_EQUAL(24u, sizeof(FastOS_Time));
    EXPECT_EQUAL(8u, sizeof(FNET_Context));
    EXPECT_EQUAL(8u, sizeof(fastos::TimeStamp));
    EXPECT_EQUAL(48u, sizeof(FastOS_Mutex));
    EXPECT_EQUAL(40u, sizeof(pthread_mutex_t));
    EXPECT_EQUAL(48u, sizeof(pthread_cond_t));
    EXPECT_EQUAL(40u, sizeof(std::mutex));
    EXPECT_EQUAL(48u, sizeof(std::condition_variable));
}

TEST_MAIN() { TEST_RUN_ALL(); }
<commit_msg>Update with current size<commit_after>// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/vespalib/testkit/test_kit.h>
#include <vespa/fnet/frt/frt.h>
#include <mutex>
#include <condition_variable>

struct RPC : public FRT_Invokable
{
  void GetInfo(FRT_RPCRequest *req)
  {
    req->GetReturn()->AddString("fastos X current");
    req->GetReturn()->AddString(FNET_Info::GetFNETVersion());
    const char *endian_str = "UNKNOWN";
    if (FNET_Info::GetEndian() == FNET_Info::ENDIAN_LITTLE)
      endian_str = "LITTLE";
    if (FNET_Info::GetEndian() == FNET_Info::ENDIAN_BIG)
      endian_str = "BIG";
    req->GetReturn()->AddString(endian_str);
    req->GetReturn()->AddInt32(FD_SETSIZE);
    req->GetReturn()->AddInt32(sizeof(FRT_RPCRequest));
  }

  void Init(FRT_Supervisor *s)
  {
    FRT_ReflectionBuilder rb(s);
    //-------------------------------------------------------------------
    rb.DefineMethod("getInfo", "", "sssii", true,
                    FRT_METHOD(RPC::GetInfo), this);
    // FastOS version
    // FNET version
    // endian
    // FD_SETSIZE
    // req object size
    //-------------------------------------------------------------------
  }
};

TEST("info") {
    RPC rpc;
    FRT_Supervisor orb;
    char spec[64];
    rpc.Init(&orb);
    ASSERT_TRUE(orb.Listen("tcp/0"));
    sprintf(spec, "tcp/localhost:%d", orb.GetListenPort());
    ASSERT_TRUE(orb.Start());

    FRT_Target     *target      = orb.GetTarget(spec);
    FRT_RPCRequest *local_info  = orb.AllocRPCRequest();
    FRT_RPCRequest *remote_info = orb.AllocRPCRequest();

    rpc.GetInfo(local_info);
    remote_info->SetMethodName("getInfo");
    target->InvokeSync(remote_info, 10.0);
    EXPECT_FALSE(remote_info->IsError());

    FRT_Values &l = *local_info->GetReturn();
 // FRT_Values &r = *remote_info->GetReturn();

    fprintf(stderr, "FastOS Version: %s\n", l[0]._string._str);
    fprintf(stderr, "FNET Version: %s\n", l[1]._string._str);
    fprintf(stderr, "Endian: %s\n", l[2]._string._str);
    fprintf(stderr, "FD_SETSIZE: %d\n", l[3]._intval32);
    fprintf(stderr, "sizeof(FRT_RPCRequest): %d\n", l[4]._intval32);

    target->SubRef();
    local_info->SubRef();
    remote_info->SubRef();
    orb.ShutDown(true);
};

TEST("size of important objects")
{
    EXPECT_EQUAL(184u, sizeof(FNET_IOComponent));
    EXPECT_EQUAL(32u, sizeof(FNET_Channel));
    EXPECT_EQUAL(40u, sizeof(FNET_PacketQueue_NoLock));
    EXPECT_EQUAL(480u, sizeof(FNET_Connection));
    EXPECT_EQUAL(96u, sizeof(FNET_Cond));
    EXPECT_EQUAL(56u, sizeof(FNET_DataBuffer));
    EXPECT_EQUAL(24u, sizeof(FastOS_Time));
    EXPECT_EQUAL(8u, sizeof(FNET_Context));
    EXPECT_EQUAL(8u, sizeof(fastos::TimeStamp));
    EXPECT_EQUAL(48u, sizeof(FastOS_Mutex));
    EXPECT_EQUAL(40u, sizeof(pthread_mutex_t));
    EXPECT_EQUAL(48u, sizeof(pthread_cond_t));
    EXPECT_EQUAL(40u, sizeof(std::mutex));
    EXPECT_EQUAL(48u, sizeof(std::condition_variable));
}

TEST_MAIN() { TEST_RUN_ALL(); }
<|endoftext|>


<commit_before>/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/detail/MemoryIdler.h>

#include <climits>
#include <cstdio>
#include <cstring>
#include <utility>

#include <folly/GLog.h>
#include <folly/Portability.h>
#include <folly/ScopeGuard.h>
#include <folly/concurrency/CacheLocality.h>
#include <folly/memory/MallctlHelper.h>
#include <folly/memory/Malloc.h>
#include <folly/portability/GFlags.h>
#include <folly/portability/PThread.h>
#include <folly/portability/SysMman.h>
#include <folly/portability/Unistd.h>

DEFINE_bool(
    folly_memory_idler_purge_arenas,
    true,
    "if enabled, folly memory-idler purges jemalloc arenas on thread idle");

namespace folly {
namespace detail {

AtomicStruct<std::chrono::steady_clock::duration>
    MemoryIdler::defaultIdleTimeout(std::chrono::seconds(5));

void MemoryIdler::flushLocalMallocCaches() {
  if (!usingJEMalloc()) {
    return;
  }
  if (!mallctl || !mallctlnametomib || !mallctlbymib) {
    FB_LOG_EVERY_MS(ERROR, 10000) << "mallctl* weak link failed";
    return;
  }

  // Not using mallctlCall as this will fail if tcache is disabled.
  mallctl("thread.tcache.flush", nullptr, nullptr, nullptr, 0);

  if (FLAGS_folly_memory_idler_purge_arenas) {
    try {
      // By default jemalloc has 4 arenas per cpu, and then assigns each
      // thread to one of those arenas.  This means that in any service
      // that doesn't perform a lot of context switching, the chances that
      // another thread will be using the current thread's arena (and hence
      // doing the appropriate dirty-page purging) are low.  Some good
      // tuned configurations (such as that used by hhvm) use fewer arenas
      // and then pin threads to avoid contended access.  In that case,
      // purging the arenas is counter-productive.  We use the heuristic
      // that if narenas <= 2 * num_cpus then we shouldn't do anything here,
      // which detects when the narenas has been reduced from the default
      unsigned narenas;
      unsigned arenaForCurrent;
      size_t mib[3];
      size_t miblen = 3;

      mallctlRead("opt.narenas", &narenas);
      mallctlRead("thread.arena", &arenaForCurrent);
      if (narenas > 2 * CacheLocality::system().numCpus &&
          mallctlnametomib("arena.0.purge", mib, &miblen) == 0) {
        mib[1] = static_cast<size_t>(arenaForCurrent);
        mallctlbymib(mib, miblen, nullptr, nullptr, nullptr, 0);
      }
    } catch (const std::runtime_error& ex) {
      FB_LOG_EVERY_MS(WARNING, 10000) << ex.what();
    }
  }
}

// Stack madvise isn't Linux or glibc specific, but the system calls
// and arithmetic (and bug compatibility) are not portable.  The set of
// platforms could be increased if it was useful.
#if (FOLLY_X64 || FOLLY_PPC64) && defined(_GNU_SOURCE) && \
    defined(__linux__) && !FOLLY_MOBILE && !FOLLY_SANITIZE_ADDRESS

static thread_local uintptr_t tls_stackLimit;
static thread_local size_t tls_stackSize;

static size_t pageSize() {
  static const size_t s_pageSize = sysconf(_SC_PAGESIZE);
  return s_pageSize;
}

static void fetchStackLimits() {
  int err;
  pthread_attr_t attr;
  if ((err = pthread_getattr_np(pthread_self(), &attr))) {
    // some restricted environments can't access /proc
    FB_LOG_ONCE(ERROR) << "pthread_getaddr_np failed errno=" << err;
    tls_stackSize = 1;
    return;
  }
  SCOPE_EXIT { pthread_attr_destroy(&attr); };

  void* addr;
  size_t rawSize;
  if ((err = pthread_attr_getstack(&attr, &addr, &rawSize))) {
    // unexpected, but it is better to continue in prod than do nothing
    FB_LOG_ONCE(ERROR) << "pthread_attr_getstack error " << err;
    assert(false);
    tls_stackSize = 1;
    return;
  }
  if (rawSize >= (1ULL << 32)) {
    // Avoid unmapping huge swaths of memory if there is an insane
    // stack size.  The boundary of sanity is somewhat arbitrary: 4GB.
    //
    // If we went into /proc to find the actual contiguous mapped pages
    // before unmapping we wouldn't care about the stack size at all,
    // but our current strategy is to unmap the entire range that might
    // be used for the stack even if it hasn't been fully faulted-in.
    //
    // Very large stack size is a bug (hence the assert), but we can
    // carry on if we are in prod.
    FB_LOG_ONCE(ERROR) << "pthread_attr_getstack returned insane stack size "
                       << rawSize;
    assert(false);
    tls_stackSize = 1;
    return;
  }
  assert(addr != nullptr);
  assert(
      0 < PTHREAD_STACK_MIN &&
      rawSize >= static_cast<size_t>(PTHREAD_STACK_MIN));

  // glibc subtracts guard page from stack size, even though pthread docs
  // seem to imply the opposite
  size_t guardSize;
  if (pthread_attr_getguardsize(&attr, &guardSize) != 0) {
    guardSize = 0;
  }
  assert(rawSize > guardSize);

  // stack goes down, so guard page adds to the base addr
  tls_stackLimit = reinterpret_cast<uintptr_t>(addr) + guardSize;
  tls_stackSize = rawSize - guardSize;

  assert((tls_stackLimit & (pageSize() - 1)) == 0);
}

FOLLY_NOINLINE static uintptr_t getStackPtr() {
  char marker;
  auto rv = reinterpret_cast<uintptr_t>(&marker);
  return rv;
}

void MemoryIdler::unmapUnusedStack(size_t retain) {
  if (tls_stackSize == 0) {
    fetchStackLimits();
  }
  if (tls_stackSize <= std::max(static_cast<size_t>(1), retain)) {
    // covers both missing stack info, and impossibly large retain
    return;
  }

  auto sp = getStackPtr();
  assert(sp >= tls_stackLimit);
  assert(sp - tls_stackLimit < tls_stackSize);

  auto end = (sp - retain) & ~(pageSize() - 1);
  if (end <= tls_stackLimit) {
    // no pages are eligible for unmapping
    return;
  }

  size_t len = end - tls_stackLimit;
  assert((len & (pageSize() - 1)) == 0);
  if (madvise((void*)tls_stackLimit, len, MADV_DONTNEED) != 0) {
    // It is likely that the stack vma hasn't been fully grown.  In this
    // case madvise will apply dontneed to the present vmas, then return
    // errno of ENOMEM.
    // If thread stack pages are backed by locked or huge pages, madvise will
    // fail with EINVAL. (EINVAL may also be returned if the address or length
    // are bad.) Warn in debug mode, since MemoryIdler may not function as
    // expected.
    // We can also get an EAGAIN, theoretically.
    PLOG_IF(WARNING, kIsDebug && errno == EINVAL) << "madvise failed";
    assert(errno == EAGAIN || errno == ENOMEM || errno == EINVAL);
  }
}

#else

void MemoryIdler::unmapUnusedStack(size_t /* retain */) {}

#endif

} // namespace detail
} // namespace folly
<commit_msg>Enable memory idler for aarch64<commit_after>/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/detail/MemoryIdler.h>

#include <climits>
#include <cstdio>
#include <cstring>
#include <utility>

#include <folly/GLog.h>
#include <folly/Portability.h>
#include <folly/ScopeGuard.h>
#include <folly/concurrency/CacheLocality.h>
#include <folly/memory/MallctlHelper.h>
#include <folly/memory/Malloc.h>
#include <folly/portability/GFlags.h>
#include <folly/portability/PThread.h>
#include <folly/portability/SysMman.h>
#include <folly/portability/Unistd.h>

DEFINE_bool(
    folly_memory_idler_purge_arenas,
    true,
    "if enabled, folly memory-idler purges jemalloc arenas on thread idle");

namespace folly {
namespace detail {

AtomicStruct<std::chrono::steady_clock::duration>
    MemoryIdler::defaultIdleTimeout(std::chrono::seconds(5));

void MemoryIdler::flushLocalMallocCaches() {
  if (!usingJEMalloc()) {
    return;
  }
  if (!mallctl || !mallctlnametomib || !mallctlbymib) {
    FB_LOG_EVERY_MS(ERROR, 10000) << "mallctl* weak link failed";
    return;
  }

  // Not using mallctlCall as this will fail if tcache is disabled.
  mallctl("thread.tcache.flush", nullptr, nullptr, nullptr, 0);

  if (FLAGS_folly_memory_idler_purge_arenas) {
    try {
      // By default jemalloc has 4 arenas per cpu, and then assigns each
      // thread to one of those arenas.  This means that in any service
      // that doesn't perform a lot of context switching, the chances that
      // another thread will be using the current thread's arena (and hence
      // doing the appropriate dirty-page purging) are low.  Some good
      // tuned configurations (such as that used by hhvm) use fewer arenas
      // and then pin threads to avoid contended access.  In that case,
      // purging the arenas is counter-productive.  We use the heuristic
      // that if narenas <= 2 * num_cpus then we shouldn't do anything here,
      // which detects when the narenas has been reduced from the default
      unsigned narenas;
      unsigned arenaForCurrent;
      size_t mib[3];
      size_t miblen = 3;

      mallctlRead("opt.narenas", &narenas);
      mallctlRead("thread.arena", &arenaForCurrent);
      if (narenas > 2 * CacheLocality::system().numCpus &&
          mallctlnametomib("arena.0.purge", mib, &miblen) == 0) {
        mib[1] = static_cast<size_t>(arenaForCurrent);
        mallctlbymib(mib, miblen, nullptr, nullptr, nullptr, 0);
      }
    } catch (const std::runtime_error& ex) {
      FB_LOG_EVERY_MS(WARNING, 10000) << ex.what();
    }
  }
}

// Stack madvise isn't Linux or glibc specific, but the system calls
// and arithmetic (and bug compatibility) are not portable.  The set of
// platforms could be increased if it was useful.
#if (FOLLY_X64 || FOLLY_PPC64 || FOLLY_AARCH64) && defined(_GNU_SOURCE) && \
    defined(__linux__) && !FOLLY_MOBILE && !FOLLY_SANITIZE_ADDRESS

static thread_local uintptr_t tls_stackLimit;
static thread_local size_t tls_stackSize;

static size_t pageSize() {
  static const size_t s_pageSize = sysconf(_SC_PAGESIZE);
  return s_pageSize;
}

static void fetchStackLimits() {
  int err;
  pthread_attr_t attr;
  if ((err = pthread_getattr_np(pthread_self(), &attr))) {
    // some restricted environments can't access /proc
    FB_LOG_ONCE(ERROR) << "pthread_getaddr_np failed errno=" << err;
    tls_stackSize = 1;
    return;
  }
  SCOPE_EXIT { pthread_attr_destroy(&attr); };

  void* addr;
  size_t rawSize;
  if ((err = pthread_attr_getstack(&attr, &addr, &rawSize))) {
    // unexpected, but it is better to continue in prod than do nothing
    FB_LOG_ONCE(ERROR) << "pthread_attr_getstack error " << err;
    assert(false);
    tls_stackSize = 1;
    return;
  }
  if (rawSize >= (1ULL << 32)) {
    // Avoid unmapping huge swaths of memory if there is an insane
    // stack size.  The boundary of sanity is somewhat arbitrary: 4GB.
    //
    // If we went into /proc to find the actual contiguous mapped pages
    // before unmapping we wouldn't care about the stack size at all,
    // but our current strategy is to unmap the entire range that might
    // be used for the stack even if it hasn't been fully faulted-in.
    //
    // Very large stack size is a bug (hence the assert), but we can
    // carry on if we are in prod.
    FB_LOG_ONCE(ERROR) << "pthread_attr_getstack returned insane stack size "
                       << rawSize;
    assert(false);
    tls_stackSize = 1;
    return;
  }
  assert(addr != nullptr);
  assert(
      0 < PTHREAD_STACK_MIN &&
      rawSize >= static_cast<size_t>(PTHREAD_STACK_MIN));

  // glibc subtracts guard page from stack size, even though pthread docs
  // seem to imply the opposite
  size_t guardSize;
  if (pthread_attr_getguardsize(&attr, &guardSize) != 0) {
    guardSize = 0;
  }
  assert(rawSize > guardSize);

  // stack goes down, so guard page adds to the base addr
  tls_stackLimit = reinterpret_cast<uintptr_t>(addr) + guardSize;
  tls_stackSize = rawSize - guardSize;

  assert((tls_stackLimit & (pageSize() - 1)) == 0);
}

FOLLY_NOINLINE static uintptr_t getStackPtr() {
  char marker;
  auto rv = reinterpret_cast<uintptr_t>(&marker);
  return rv;
}

void MemoryIdler::unmapUnusedStack(size_t retain) {
  if (tls_stackSize == 0) {
    fetchStackLimits();
  }
  if (tls_stackSize <= std::max(static_cast<size_t>(1), retain)) {
    // covers both missing stack info, and impossibly large retain
    return;
  }

  auto sp = getStackPtr();
  assert(sp >= tls_stackLimit);
  assert(sp - tls_stackLimit < tls_stackSize);

  auto end = (sp - retain) & ~(pageSize() - 1);
  if (end <= tls_stackLimit) {
    // no pages are eligible for unmapping
    return;
  }

  size_t len = end - tls_stackLimit;
  assert((len & (pageSize() - 1)) == 0);
  if (madvise((void*)tls_stackLimit, len, MADV_DONTNEED) != 0) {
    // It is likely that the stack vma hasn't been fully grown.  In this
    // case madvise will apply dontneed to the present vmas, then return
    // errno of ENOMEM.
    // If thread stack pages are backed by locked or huge pages, madvise will
    // fail with EINVAL. (EINVAL may also be returned if the address or length
    // are bad.) Warn in debug mode, since MemoryIdler may not function as
    // expected.
    // We can also get an EAGAIN, theoretically.
    PLOG_IF(WARNING, kIsDebug && errno == EINVAL) << "madvise failed";
    assert(errno == EAGAIN || errno == ENOMEM || errno == EINVAL);
  }
}

#else

void MemoryIdler::unmapUnusedStack(size_t /* retain */) {}

#endif

} // namespace detail
} // namespace folly
<|endoftext|>


<commit_before>/*
 * Copyright 2016 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/portability/Stdlib.h>

#ifdef _WIN32
#include <cstring>
#include <errno.h>
#include <folly/portability/Fcntl.h>
#include <folly/portability/SysStat.h>
#include <folly/portability/Windows.h>

extern "C" {
char* mktemp(char* tn) { return _mktemp(tn); }

// While yes, this is for a directory, due to this being windows,
// a file and directory can't have the same name, resulting in this
// still working just fine.
char* mkdtemp(char* tn) {
  char* ptr = nullptr;
  auto len = strlen(ptr);
  int ret = 0;
  do {
    strcpy(tn + len - 6, "XXXXXX");
    ptr = mktemp(tn);
    if (ptr == nullptr || *ptr == '\0') {
      return nullptr;
    }
    ret = mkdir(ptr, 0700);
    if (ret != 0 && errno != EEXIST) {
      return nullptr;
    }
  } while (ret != 0);
  return tn;
}

int mkstemp(char* tn) {
  char* ptr = nullptr;
  auto len = strlen(ptr);
  int ret = 0;
  do {
    strcpy(tn + len - 6, "XXXXXX");
    ptr = mktemp(tn);
    if (ptr == nullptr || *ptr == '\0') {
      return -1;
    }
    ret = open(ptr, O_RDWR | O_EXCL | O_CREAT, S_IRUSR | S_IWUSR);
    if (ret == -1 && errno != EEXIST) {
      return -1;
    }
  } while (ret == -1);
  return ret;
}

char* realpath(const char* path, char* resolved_path) {
  // I sure hope the caller gave us _MAX_PATH space in the buffer....
  return _fullpath(resolved_path, path, _MAX_PATH);
}
}
#endif
<commit_msg>Don't call strlen(nullptr) in mkdtemp and mkstemp<commit_after>/*
 * Copyright 2016 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/portability/Stdlib.h>

#ifdef _WIN32
#include <cstring>
#include <errno.h>

#include <folly/portability/Fcntl.h>
#include <folly/portability/SysStat.h>
#include <folly/portability/Windows.h>

extern "C" {
char* mktemp(char* tn) { return _mktemp(tn); }

// While yes, this is for a directory, due to this being windows,
// a file and directory can't have the same name, resulting in this
// still working just fine.
char* mkdtemp(char* tn) {
  char* ptr = nullptr;
  auto len = strlen(tn);
  int ret = 0;
  do {
    strcpy(tn + len - 6, "XXXXXX");
    ptr = mktemp(tn);
    if (ptr == nullptr || *ptr == '\0') {
      return nullptr;
    }
    ret = mkdir(ptr, 0700);
    if (ret != 0 && errno != EEXIST) {
      return nullptr;
    }
  } while (ret != 0);
  return tn;
}

int mkstemp(char* tn) {
  char* ptr = nullptr;
  auto len = strlen(tn);
  int ret = 0;
  do {
    strcpy(tn + len - 6, "XXXXXX");
    ptr = mktemp(tn);
    if (ptr == nullptr || *ptr == '\0') {
      return -1;
    }
    ret = open(ptr, O_RDWR | O_EXCL | O_CREAT, S_IRUSR | S_IWUSR);
    if (ret == -1 && errno != EEXIST) {
      return -1;
    }
  } while (ret == -1);
  return ret;
}

char* realpath(const char* path, char* resolved_path) {
  // I sure hope the caller gave us _MAX_PATH space in the buffer....
  return _fullpath(resolved_path, path, _MAX_PATH);
}
}
#endif
<|endoftext|>


<commit_before>#include "rs-config.h"

#include "../third-party/json.hpp"

#include "model-views.h"

#include <fstream>

#include "os.h"

using json = nlohmann::json;

using namespace rs2;

void config_file::set(const char* key, const char* value)
{
    _values[key] = value;
    save();
}

void config_file::set_default(const char* key, const char* calculate)
{
    _defaults[key] = calculate;
}

void config_file::reset()
{
    _values.clear();
    save();
}

std::string config_file::get(const char* key, const char* def) const
{
    auto it = _values.find(key);
    if (it != _values.end())
    {
        return it->second;
    }
    return get_default(key, def);
}

bool config_file::contains(const char* key) const
{
    auto it = _values.find(key);
    return it != _values.end();
}

std::string config_file::get_default(const char* key, const char* def) const
{
    auto it = _defaults.find(key);
    if (it == _defaults.end()) return def;
    return it->second;
}

config_value config_file::get(const char* key) const
{
    if (!contains(key)) return config_value(get_default(key, ""));
    return config_value(get(key, ""));
}

void config_file::save(const char* filename)
{
    json j;
    for(auto kvp : _values)
    {
        j[kvp.first] = kvp.second;
    }
    std::string s = j.dump(2);
    try
    {
        std::ofstream out(filename);
        out << s;
        out.close();
    }
    catch (...)
    {
    }
}

config_file& config_file::instance()
{
    static config_file inst(get_folder_path(rs2::special_folder::app_data)
                            + std::string("realsense-config.json"));
    return inst;
}

config_file::config_file(std::string filename)
    : _filename(std::move(filename)), _values()
{
    try
    {

        std::ifstream t(_filename);
        if (!t.good()) return;
        std::string str((std::istreambuf_iterator<char>(t)),
                 std::istreambuf_iterator<char>());
        auto j = json::parse(str);
        for (json::iterator it = j.begin(); it != j.end(); ++it)
        {
            _values[it.key()] = it.value();
        }
    }
    catch(...)
    {

    }
}

void config_file::save()
{
    save(_filename.c_str());
}

config_file::config_file()
    : _filename(""), _values()
{
}

config_file& config_file::operator=(const config_file& other)
{
    if (this != &other)
    {
        _values = other._values;
        _defaults = other._defaults;
        save();
    }
    return *this;
}

bool config_file::operator==(const config_file& other) const
{
    return _values == other._values;
}<commit_msg>Visual Studio 2019 compilation fix<commit_after>#include "rs-config.h"

#include "../third-party/json.hpp"

#include "model-views.h"

#include <fstream>

#include "os.h"

using json = nlohmann::json;

using namespace rs2;

void config_file::set(const char* key, const char* value)
{
    _values[key] = value;
    save();
}

void config_file::set_default(const char* key, const char* calculate)
{
    _defaults[key] = calculate;
}

void config_file::reset()
{
    _values.clear();
    save();
}

std::string config_file::get(const char* key, const char* def) const
{
    auto it = _values.find(key);
    if (it != _values.end())
    {
        return it->second;
    }
    return get_default(key, def);
}

bool config_file::contains(const char* key) const
{
    auto it = _values.find(key);
    return it != _values.end();
}

std::string config_file::get_default(const char* key, const char* def) const
{
    auto it = _defaults.find(key);
    if (it == _defaults.end()) return def;
    return it->second;
}

config_value config_file::get(const char* key) const
{
    if (!contains(key)) return config_value(get_default(key, ""));
    return config_value(get(key, ""));
}

void config_file::save(const char* filename)
{
    json j;
    for(auto kvp : _values)
    {
        j[kvp.first] = kvp.second;
    }
    std::string s = j.dump(2);
    try
    {
        std::ofstream out(filename);
        out << s;
        out.close();
    }
    catch (...)
    {
    }
}

config_file& config_file::instance()
{
    static config_file inst(get_folder_path(rs2::special_folder::app_data)
                            + std::string("realsense-config.json"));
    return inst;
}

config_file::config_file(std::string filename)
    : _filename(std::move(filename)), _values()
{
    try
    {

        std::ifstream t(_filename);
        if (!t.good()) return;
        std::string str((std::istreambuf_iterator<char>(t)),
                 std::istreambuf_iterator<char>());
        auto j = json::parse(str);
        for (json::iterator it = j.begin(); it != j.end(); ++it)
        {
            _values[it.key()] = it.value().get<std::string>();
        }
    }
    catch(...)
    {

    }
}

void config_file::save()
{
    save(_filename.c_str());
}

config_file::config_file()
    : _filename(""), _values()
{
}

config_file& config_file::operator=(const config_file& other)
{
    if (this != &other)
    {
        _values = other._values;
        _defaults = other._defaults;
        save();
    }
    return *this;
}

bool config_file::operator==(const config_file& other) const
{
    return _values == other._values;
}<|endoftext|>


<commit_before>#include "cinder/app/AppNative.h"
#include "cinder/gl/gl.h"
#include "cinder/ImageIo.h"
#include "cinder/gl/Texture.h"
#include "cinder/Surface.h"
#include "cinder/gl/Fbo.h"
#include "cinder/Filesystem.h"
#include "cinder/app/FileDropEvent.h"
#include "cinder/ip/Resize.h"
#include "ParticleEmitter.h"
#include "SimpleGUI.h"

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

using namespace ci;
using namespace ci::app;
using namespace std;
using namespace mowa::sgui;

#define SGUI_CONFIG_FILE_EXT "cfg"

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class CinderApp : public AppNative
{
public:
	void setup();

  // mouse events
  void mouseDown( ci::app::MouseEvent     _event );
	void mouseUp(   ci::app::MouseEvent     _event );
  void mouseDrag( ci::app::MouseEvent     _event );
	void fileDrop ( ci::app::FileDropEvent  _event );
  void keyDown(   ci::app::KeyEvent       _event );

  // gui buttons
  bool openImageCallBack( ci::app::MouseEvent _event );
  bool nextImageCallBack( ci::app::MouseEvent _event );

	// misc routines
  void updateOutputArea( Vec2i& _imageSize );
  void setImage( fs::path& _path, double _currentTime = 0.0 );

  // main routines
  void update();
	void draw();
  void prepareSettings( Settings *settings );

  // properties
  Surface                     m_surface;
  gl::Texture                 m_texture;
  Area                        m_outputArea;
  ParticleEmitter             m_particleEmitter;
  gl::Fbo                     m_frameBufferObject;
  std::vector< ci::fs::path > m_files;
  double                      m_cycleImageEvery;
  int                         m_particleCount;
  int                         m_particleGroups;

  SimpleGUI*                  m_gui;
  ButtonControl*              m_openImageButton;
  ButtonControl*              m_nextImageButton;
  LabelControl*               m_currentImageLabel;
	PanelControl*               m_mainPanel;
	PanelControl*               m_helpPanel;

private:
  double                      m_lastTime;
  double                      m_currentTime;
  double                      m_cycleCounter;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

void CinderApp::setup()
{
  // config vars
  m_cycleImageEvery = 0.0;
  m_particleCount   = 0;
  m_particleGroups  = 0;

  // buffer for trails
  m_frameBufferObject = gl::Fbo( 800, 600, true );
  m_frameBufferObject.bindFramebuffer();
  gl::enableAlphaBlending();
  gl::clear( Color( 0.0f, 0.0f, 0.0f ) );
  m_frameBufferObject.unbindFramebuffer();

  // emitter
  m_particleEmitter.m_maxLifeTime        = 0.0;
  m_particleEmitter.m_minLifeTime        = 10.0;
  m_particleEmitter.m_fadeInTime         = 0.5f;
  m_particleEmitter.m_fadeOutTime        = 3.0f;
  m_particleEmitter.m_referenceSurface   = &m_surface;
  m_particleEmitter.m_screenTexture      = &m_frameBufferObject.getTexture();
  m_particleEmitter.m_particlesPerSecond = 0;

  // GUI
  m_gui             = new SimpleGUI( this );
	m_gui->lightColor = ColorA( 1, 1, 0, 1 );
  m_gui->textFont   = Font( "Consolas", 12 );
  m_gui->addColumn();
  m_mainPanel = m_gui->addPanel();

  // general settings
  m_gui->addLabel( "General Settings" );
	m_gui->addParam( "Pic. Cycle Time", &m_cycleImageEvery,                       3.0f, 120.0f, 15.0f );
  m_gui->addParam( "Particle Size",   &m_particleEmitter.m_particleSizeRatio,   0.5f,   3.0f,  1.0f );
  m_gui->addParam( "Particle Speed",  &m_particleEmitter.m_particleSpeedRatio,  0.2f,   3.0f,  1.0f );
  m_gui->addParam( "Dampness",        &m_particleEmitter.m_dampness,           0.01f,  0.99f,  0.9f );
  m_gui->addParam( "Color Guidance",  &m_particleEmitter.m_colorRedirection,    0.0f,  20.0f,  5.0f );

#ifdef _DEBUG
  m_gui->addParam( "#Particles", &m_particleCount,   50,   500,    150   );
  m_gui->addParam( "#Groups",    &m_particleGroups,  1,    10,     2   );
#else
  m_gui->addParam( "#Particles", &m_particleCount,   50,   500,    300   );
  m_gui->addParam( "#Groups",    &m_particleGroups,  1,    10,     5   );
#endif

	m_gui->addSeparator();
  m_gui->addLabel( "Flocking Settings" );

  m_gui->addParam( "Repel Str.",      &m_particleEmitter.m_repelStrength,       0.000f,     0.5f,   0.04f );
  m_gui->addParam( "Align Str.",      &m_particleEmitter.m_alignStrength,       0.000f,     0.5f,   0.04f );
  m_gui->addParam( "Att. Str.",       &m_particleEmitter.m_attractStrength,     0.000f,     0.5f,   0.02f );
  m_gui->addParam( "Grp. Repel Str.", &m_particleEmitter.m_groupRepelStrength,  0.000f,     0.5f,   0.01f );
  m_gui->addParam( "Area Size",       &m_particleEmitter.m_zoneRadiusSqrd,      625.0f, 10000.0f, 5625.0f ),
  m_gui->addParam( "Repel Area",      &m_particleEmitter.m_lowThresh,             0.0f,     1.0f,  0.125f );
  m_gui->addParam( "Align Area",      &m_particleEmitter.m_highThresh,            0.0f,     1.0f,   0.65f );

  m_gui->addSeparator();

  m_openImageButton = m_gui->addButton( "Open Image" );
  m_openImageButton->registerClick( this, &CinderApp::openImageCallBack );

  m_nextImageButton = m_gui->addButton( "Next Image" );
  m_nextImageButton->registerClick( this, &CinderApp::nextImageCallBack );

  // some info
  m_gui->addSeparator();
  m_gui->addLabel( "Current Image:" );
  m_currentImageLabel = m_gui->addLabel( "" );

  // deserved credits
  m_gui->addSeparator();
  m_gui->addLabel( "y3i12: Yuri Ivatchkovitch" );
  m_gui->addLabel( "http://y3i12.tumblr.com/"  );

  // Help!
  m_gui->addColumn();
  m_helpPanel = m_gui->addPanel();

  m_gui->addLabel( "Quick Help:"            );
  m_gui->addLabel( "F1  to show/hide help"  );
	m_gui->addLabel( "'h' to show/hide GUI"   );
  m_gui->addLabel( "'s' to save config"     );
  m_gui->addLabel( "'l' to load config"     );
  m_gui->addLabel( "'o' to open image"      );
  m_gui->addLabel( "SPACE to skip image"    );
  m_gui->addLabel( "ESC to quit"            );

  m_gui->addSeparator();
  m_gui->addSeparator();

  m_gui->addLabel( "Drag multiple files"    );
  m_gui->addLabel( "to slideshow!"          );

  // load images passed via args
  if ( getArgs().size() > 1 )
  {
    const std::vector< std::string >& args = getArgs();

    for ( size_t i = 1; i < args.size(); ++i )
    {
      m_files.push_back( fs::canonical( fs::path( args[ i ] ) ) );
    }

    setImage( m_files.front(), m_currentTime );
    if ( m_files.size() > 1 )
    {
      m_cycleCounter = 0;
    }
  }
  else
  {
    m_cycleCounter  = -1.0;
  }

  // mark the time to test counters
  m_lastTime = ci::app::getElapsedSeconds();
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::mouseDown( ci::app::MouseEvent _event )
{
  m_gui->onMouseDown( _event );
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::mouseUp(  ci::app::MouseEvent _event )
{
    m_gui->onMouseUp( _event );
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::mouseDrag( ci::app::MouseEvent _event )
{
  m_gui->onMouseDrag( _event );
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::fileDrop (ci::app::FileDropEvent e)
{
  m_files = e.getFiles();

  setImage( m_files.front(), m_currentTime );

  if ( m_files.size() > 1 )
  {
    m_cycleCounter = 0;
  }
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::keyDown( ci::app::KeyEvent _event )
{
  switch( _event.getChar() )
  {
		case 'd':
      {
        m_gui->dump();
      }
      break; //prints values of all the controls to the console

		case 'l':
      {
        std::vector< std::string > theExtensions;
        theExtensions.push_back( SGUI_CONFIG_FILE_EXT );

        fs::path aPath = getOpenFilePath( "", theExtensions );
        if ( !aPath.empty() && fs::is_regular_file( aPath ) )
        {
          m_gui->load( aPath.string() );
        }
      }
      break;

		case 's':
      {
        std::vector< std::string > theExtensions;
        theExtensions.push_back( SGUI_CONFIG_FILE_EXT );

        fs::path aPath = getSaveFilePath( "", theExtensions );
        if ( !aPath.empty() )
        {
          if ( aPath.extension() != SGUI_CONFIG_FILE_EXT )
          {
            aPath.replace_extension( SGUI_CONFIG_FILE_EXT );
          }

          m_gui->save( aPath.string() );
        }
      }
      break;

    case 'h':
      {
        m_mainPanel->enabled = !m_mainPanel->enabled;
      }

    case 'o':
      {
        openImageCallBack( ci::app::MouseEvent() );
      }
      break;
	}

	switch(_event.getCode())
  {
    case KeyEvent::KEY_ESCAPE:
      {
        quit();
      }
      break;

    case KeyEvent::KEY_F1:
      {
        m_helpPanel->enabled = !m_helpPanel->enabled;
      }
      break;

    case KeyEvent::KEY_SPACE:
      {
        nextImageCallBack( ci::app::MouseEvent() );
      }
      break;
	}
}

////////////////////////////////////////////////////////////////////////////////

bool CinderApp::openImageCallBack( ci::app::MouseEvent _event )
{
  std::vector< std::string > theExtensions;
  theExtensions.push_back( "jpg" );

  m_cycleCounter  = -1.0;
  m_files.clear();

  fs::path aPath = getOpenFilePath( "", theExtensions );
  if ( !aPath.empty() && fs::is_regular_file( aPath ) )
  {
    setImage( aPath, m_currentTime );
  }
  return false;
}

////////////////////////////////////////////////////////////////////////////////

bool CinderApp::nextImageCallBack( ci::app::MouseEvent _event )
{
  m_cycleCounter = m_cycleImageEvery;
  return false;
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::updateOutputArea( Vec2i& _imageSize )
{
    m_outputArea.x1 = m_outputArea.x2 = static_cast< int >( getWindowCenter().x );
    m_outputArea.y1 = m_outputArea.y2 = static_cast< int >( getWindowCenter().y );

    m_outputArea.x1 -= _imageSize.x / 2;
    m_outputArea.x2 += _imageSize.x / 2;
    m_outputArea.y1 -= _imageSize.y / 2;
    m_outputArea.y2 += _imageSize.y / 2;

    m_particleEmitter.m_position = Vec2f( static_cast< float >( m_outputArea.x1 ), static_cast< float >( m_outputArea.y1 ) );
}

void CinderApp::setImage( fs::path& _path, double _currentTime )
{
  // load the image, resize and set the texture
  ci::Surface imageLoaded = loadImage( _path );
  Vec2i       aSize       = imageLoaded.getSize();
  float       theFactor   = min( static_cast< float >( getWindowSize().x ) / aSize.x, static_cast< float >( getWindowSize().y ) / aSize.y );

  m_surface = ci::Surface( static_cast< int >( aSize.x * theFactor ), static_cast< int >( aSize.y * theFactor ), false );
  ci::ip::resize( imageLoaded, imageLoaded.getBounds(), &m_surface, m_surface.getBounds() );
  m_texture = m_surface;

  // update  the image name
  m_currentImageLabel->setText( _path.filename().string() );

  // update the output area
  updateOutputArea( m_surface.getSize() );

  // kill old particles and add new ones
  m_particleEmitter.killAll( _currentTime );

  for ( int i = 0; i < m_particleGroups; ++i )
  {
    m_particleEmitter.addParticles( m_particleCount, i );
  }

  // resets the cycle counter;
  m_cycleCounter = -1.0;
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::update()
{
  m_currentTime = ci::app::getElapsedSeconds();
  double delta  = m_currentTime - m_lastTime;

  if ( m_cycleCounter != -1.0 )
  {
    m_cycleCounter += delta;

    if ( m_cycleCounter >= m_cycleImageEvery && m_files.size() > 1 )
    {
      m_cycleCounter -= m_cycleImageEvery;

      ci::fs::path aPath = m_files.front();
      m_files.erase( m_files.begin() );
      m_files.push_back( aPath );

      setImage( aPath, m_currentTime );
    }
  }

  m_particleEmitter.update( m_currentTime, delta );

  m_lastTime = m_currentTime;
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::draw()
{
	// clear out the window with black and set gl confs
	gl::clear( Color( 0.0f, 0.0f, 0.0f ) );
  gl::disableDepthRead();
  gl::pushMatrices();
  gl::translate( Vec3f( 0.0f, 0.0f, 0.0f ) );

  // writes backed up frame buffer to the screen
  m_frameBufferObject.blitToScreen( getWindowBounds(), getWindowBounds() );

  // darkens the BG
  gl::enableAlphaBlending();
  gl::color( 0.0f, 0.0f, 0.0f, 0.01f );
  gl::drawSolidRect( getWindowBounds() );

  // do the drawing =D
  m_particleEmitter.draw();

  // save what happened to the framebuffer
  m_frameBufferObject.blitFromScreen( getWindowBounds(), getWindowBounds() );

  // reset gl confs
  gl::enableDepthRead();
  gl::disableAlphaBlending();
  gl::popMatrices();

  // draw the UI
  m_gui->draw();
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::prepareSettings( Settings *settings )
{
  settings->setWindowSize( 800, 600 );
  settings->setFrameRate( 60.0f );
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

CINDER_APP_NATIVE( CinderApp, RendererGl )

////////////////////////////////////////////////////////////////////////////////

<commit_msg>Fix on the cycle counter<commit_after>#include "cinder/app/AppNative.h"
#include "cinder/gl/gl.h"
#include "cinder/ImageIo.h"
#include "cinder/gl/Texture.h"
#include "cinder/Surface.h"
#include "cinder/gl/Fbo.h"
#include "cinder/Filesystem.h"
#include "cinder/app/FileDropEvent.h"
#include "cinder/ip/Resize.h"
#include "ParticleEmitter.h"
#include "SimpleGUI.h"

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

using namespace ci;
using namespace ci::app;
using namespace std;
using namespace mowa::sgui;

#define SGUI_CONFIG_FILE_EXT "cfg"

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class CinderApp : public AppNative
{
public:
	void setup();

  // mouse events
  void mouseDown( ci::app::MouseEvent     _event );
	void mouseUp(   ci::app::MouseEvent     _event );
  void mouseDrag( ci::app::MouseEvent     _event );
	void fileDrop ( ci::app::FileDropEvent  _event );
  void keyDown(   ci::app::KeyEvent       _event );

  // gui buttons
  bool openImageCallBack( ci::app::MouseEvent _event );
  bool nextImageCallBack( ci::app::MouseEvent _event );

	// misc routines
  void updateOutputArea( Vec2i& _imageSize );
  void setImage( fs::path& _path, double _currentTime = 0.0 );

  // main routines
  void update();
	void draw();
  void prepareSettings( Settings *settings );

  // properties
  Surface                     m_surface;
  gl::Texture                 m_texture;
  Area                        m_outputArea;
  ParticleEmitter             m_particleEmitter;
  gl::Fbo                     m_frameBufferObject;
  std::vector< ci::fs::path > m_files;
  double                      m_cycleImageEvery;
  int                         m_particleCount;
  int                         m_particleGroups;

  SimpleGUI*                  m_gui;
  ButtonControl*              m_openImageButton;
  ButtonControl*              m_nextImageButton;
  LabelControl*               m_currentImageLabel;
	PanelControl*               m_mainPanel;
	PanelControl*               m_helpPanel;

private:
  double                      m_lastTime;
  double                      m_currentTime;
  double                      m_cycleCounter;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

void CinderApp::setup()
{
  // config vars
  m_cycleImageEvery = 0.0;
  m_particleCount   = 0;
  m_particleGroups  = 0;

  // buffer for trails
  m_frameBufferObject = gl::Fbo( 800, 600, true );
  m_frameBufferObject.bindFramebuffer();
  gl::enableAlphaBlending();
  gl::clear( Color( 0.0f, 0.0f, 0.0f ) );
  m_frameBufferObject.unbindFramebuffer();

  // emitter
  m_particleEmitter.m_maxLifeTime        = 0.0;
  m_particleEmitter.m_minLifeTime        = 10.0;
  m_particleEmitter.m_fadeInTime         = 0.5f;
  m_particleEmitter.m_fadeOutTime        = 3.0f;
  m_particleEmitter.m_referenceSurface   = &m_surface;
  m_particleEmitter.m_screenTexture      = &m_frameBufferObject.getTexture();
  m_particleEmitter.m_particlesPerSecond = 0;

  // GUI
  m_gui             = new SimpleGUI( this );
	m_gui->lightColor = ColorA( 1, 1, 0, 1 );
  m_gui->textFont   = Font( "Consolas", 12 );
  m_gui->addColumn();
  m_mainPanel = m_gui->addPanel();

  // general settings
  m_gui->addLabel( "General Settings" );
	m_gui->addParam( "Pic. Cycle Time", &m_cycleImageEvery,                       3.0f, 120.0f, 15.0f );
  m_gui->addParam( "Particle Size",   &m_particleEmitter.m_particleSizeRatio,   0.5f,   3.0f,  1.0f );
  m_gui->addParam( "Particle Speed",  &m_particleEmitter.m_particleSpeedRatio,  0.2f,   3.0f,  1.0f );
  m_gui->addParam( "Dampness",        &m_particleEmitter.m_dampness,           0.01f,  0.99f,  0.9f );
  m_gui->addParam( "Color Guidance",  &m_particleEmitter.m_colorRedirection,    0.0f,  20.0f,  5.0f );

#ifdef _DEBUG
  m_gui->addParam( "#Particles", &m_particleCount,   50,   500,    150   );
  m_gui->addParam( "#Groups",    &m_particleGroups,  1,    10,     2   );
#else
  m_gui->addParam( "#Particles", &m_particleCount,   50,   500,    300   );
  m_gui->addParam( "#Groups",    &m_particleGroups,  1,    10,     5   );
#endif

	m_gui->addSeparator();
  m_gui->addLabel( "Flocking Settings" );

  m_gui->addParam( "Repel Str.",      &m_particleEmitter.m_repelStrength,       0.000f,     0.5f,   0.04f );
  m_gui->addParam( "Align Str.",      &m_particleEmitter.m_alignStrength,       0.000f,     0.5f,   0.04f );
  m_gui->addParam( "Att. Str.",       &m_particleEmitter.m_attractStrength,     0.000f,     0.5f,   0.02f );
  m_gui->addParam( "Grp. Repel Str.", &m_particleEmitter.m_groupRepelStrength,  0.000f,     0.5f,   0.01f );
  m_gui->addParam( "Area Size",       &m_particleEmitter.m_zoneRadiusSqrd,      625.0f, 10000.0f, 5625.0f ),
  m_gui->addParam( "Repel Area",      &m_particleEmitter.m_lowThresh,             0.0f,     1.0f,  0.125f );
  m_gui->addParam( "Align Area",      &m_particleEmitter.m_highThresh,            0.0f,     1.0f,   0.65f );

  m_gui->addSeparator();

  m_openImageButton = m_gui->addButton( "Open Image" );
  m_openImageButton->registerClick( this, &CinderApp::openImageCallBack );

  m_nextImageButton = m_gui->addButton( "Next Image" );
  m_nextImageButton->registerClick( this, &CinderApp::nextImageCallBack );

  // some info
  m_gui->addSeparator();
  m_gui->addLabel( "Current Image:" );
  m_currentImageLabel = m_gui->addLabel( "" );

  // deserved credits
  m_gui->addSeparator();
  m_gui->addLabel( "y3i12: Yuri Ivatchkovitch" );
  m_gui->addLabel( "http://y3i12.tumblr.com/"  );

  // Help!
  m_gui->addColumn();
  m_helpPanel = m_gui->addPanel();

  m_gui->addLabel( "Quick Help:"            );
  m_gui->addLabel( "F1  to show/hide help"  );
	m_gui->addLabel( "'h' to show/hide GUI"   );
  m_gui->addLabel( "'s' to save config"     );
  m_gui->addLabel( "'l' to load config"     );
  m_gui->addLabel( "'o' to open image"      );
  m_gui->addLabel( "SPACE to skip image"    );
  m_gui->addLabel( "ESC to quit"            );

  m_gui->addSeparator();
  m_gui->addSeparator();

  m_gui->addLabel( "Drag multiple files"    );
  m_gui->addLabel( "to slideshow!"          );

  // load images passed via args
  if ( getArgs().size() > 1 )
  {
    const std::vector< std::string >& args = getArgs();

    for ( size_t i = 1; i < args.size(); ++i )
    {
      m_files.push_back( fs::canonical( fs::path( args[ i ] ) ) );
    }

    setImage( m_files.front(), m_currentTime );
    if ( m_files.size() > 1 )
    {
      m_cycleCounter = 0;
    }
  }
  else
  {
    m_cycleCounter  = -1.0;
  }

  // mark the time to test counters
  m_lastTime = ci::app::getElapsedSeconds();
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::mouseDown( ci::app::MouseEvent _event )
{
  m_gui->onMouseDown( _event );
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::mouseUp(  ci::app::MouseEvent _event )
{
    m_gui->onMouseUp( _event );
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::mouseDrag( ci::app::MouseEvent _event )
{
  m_gui->onMouseDrag( _event );
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::fileDrop (ci::app::FileDropEvent e)
{
  m_files = e.getFiles();

  setImage( m_files.front(), m_currentTime );

  if ( m_files.size() > 1 )
  {
    m_cycleCounter = 0;
  }
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::keyDown( ci::app::KeyEvent _event )
{
  switch( _event.getChar() )
  {
		case 'd':
      {
        m_gui->dump();
      }
      break; //prints values of all the controls to the console

		case 'l':
      {
        std::vector< std::string > theExtensions;
        theExtensions.push_back( SGUI_CONFIG_FILE_EXT );

        fs::path aPath = getOpenFilePath( "", theExtensions );
        if ( !aPath.empty() && fs::is_regular_file( aPath ) )
        {
          m_gui->load( aPath.string() );
        }
      }
      break;

		case 's':
      {
        std::vector< std::string > theExtensions;
        theExtensions.push_back( SGUI_CONFIG_FILE_EXT );

        fs::path aPath = getSaveFilePath( "", theExtensions );
        if ( !aPath.empty() )
        {
          if ( aPath.extension() != SGUI_CONFIG_FILE_EXT )
          {
            aPath.replace_extension( SGUI_CONFIG_FILE_EXT );
          }

          m_gui->save( aPath.string() );
        }
      }
      break;

    case 'h':
      {
        m_mainPanel->enabled = !m_mainPanel->enabled;
      }
      break;

    case 'o':
      {
        openImageCallBack( ci::app::MouseEvent() );
      }
      break;
	}

	switch(_event.getCode())
  {
    case KeyEvent::KEY_ESCAPE:
      {
        quit();
      }
      break;

    case KeyEvent::KEY_F1:
      {
        m_helpPanel->enabled = !m_helpPanel->enabled;
      }
      break;

    case KeyEvent::KEY_SPACE:
      {
        nextImageCallBack( ci::app::MouseEvent() );
      }
      break;
	}
}

////////////////////////////////////////////////////////////////////////////////

bool CinderApp::openImageCallBack( ci::app::MouseEvent _event )
{
  std::vector< std::string > theExtensions;
  theExtensions.push_back( "jpg" );

  m_files.clear();

  fs::path aPath = getOpenFilePath( "", theExtensions );
  if ( !aPath.empty() && fs::is_regular_file( aPath ) )
  {
    setImage( aPath, m_currentTime );
  }

  m_cycleCounter  = -1.0;

  return false;
}

////////////////////////////////////////////////////////////////////////////////

bool CinderApp::nextImageCallBack( ci::app::MouseEvent _event )
{
  m_cycleCounter = m_cycleImageEvery;
  return false;
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::updateOutputArea( Vec2i& _imageSize )
{
    m_outputArea.x1 = m_outputArea.x2 = static_cast< int >( getWindowCenter().x );
    m_outputArea.y1 = m_outputArea.y2 = static_cast< int >( getWindowCenter().y );

    m_outputArea.x1 -= _imageSize.x / 2;
    m_outputArea.x2 += _imageSize.x / 2;
    m_outputArea.y1 -= _imageSize.y / 2;
    m_outputArea.y2 += _imageSize.y / 2;

    m_particleEmitter.m_position = Vec2f( static_cast< float >( m_outputArea.x1 ), static_cast< float >( m_outputArea.y1 ) );
}

void CinderApp::setImage( fs::path& _path, double _currentTime )
{
  // load the image, resize and set the texture
  ci::Surface imageLoaded = loadImage( _path );
  Vec2i       aSize       = imageLoaded.getSize();
  float       theFactor   = min( static_cast< float >( getWindowSize().x ) / aSize.x, static_cast< float >( getWindowSize().y ) / aSize.y );

  m_surface = ci::Surface( static_cast< int >( aSize.x * theFactor ), static_cast< int >( aSize.y * theFactor ), false );
  ci::ip::resize( imageLoaded, imageLoaded.getBounds(), &m_surface, m_surface.getBounds() );
  m_texture = m_surface;

  // update  the image name
  m_currentImageLabel->setText( _path.filename().string() );

  // update the output area
  updateOutputArea( m_surface.getSize() );

  // kill old particles and add new ones
  m_particleEmitter.killAll( _currentTime );

  for ( int i = 0; i < m_particleGroups; ++i )
  {
    m_particleEmitter.addParticles( m_particleCount, i );
  }

  // resets the cycle counter;
  m_cycleCounter = 0.0;
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::update()
{
  m_currentTime = ci::app::getElapsedSeconds();
  double delta  = m_currentTime - m_lastTime;

  if ( m_cycleCounter != -1.0 )
  {
    m_cycleCounter += delta;

    if ( m_cycleCounter >= m_cycleImageEvery && m_files.size() > 1 )
    {
      m_cycleCounter -= m_cycleImageEvery;

      ci::fs::path aPath = m_files.front();
      m_files.erase( m_files.begin() );
      m_files.push_back( aPath );

      setImage( aPath, m_currentTime );
    }
  }

  m_particleEmitter.update( m_currentTime, delta );

  m_lastTime = m_currentTime;
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::draw()
{
	// clear out the window with black and set gl confs
	gl::clear( Color( 0.0f, 0.0f, 0.0f ) );
  gl::disableDepthRead();
  gl::pushMatrices();
  gl::translate( Vec3f( 0.0f, 0.0f, 0.0f ) );

  // writes backed up frame buffer to the screen
  m_frameBufferObject.blitToScreen( getWindowBounds(), getWindowBounds() );

  // darkens the BG
  gl::enableAlphaBlending();
  gl::color( 0.0f, 0.0f, 0.0f, 0.01f );
  gl::drawSolidRect( getWindowBounds() );

  // do the drawing =D
  m_particleEmitter.draw();

  // save what happened to the framebuffer
  m_frameBufferObject.blitFromScreen( getWindowBounds(), getWindowBounds() );

  // reset gl confs
  gl::enableDepthRead();
  gl::disableAlphaBlending();
  gl::popMatrices();

  // draw the UI
  m_gui->draw();
}

////////////////////////////////////////////////////////////////////////////////

void CinderApp::prepareSettings( Settings *settings )
{
  settings->setWindowSize( 800, 600 );
  settings->setFrameRate( 60.0f );
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

CINDER_APP_NATIVE( CinderApp, RendererGl )

////////////////////////////////////////////////////////////////////////////////

<|endoftext|>