<commit_before>//
//  ClockReceiver.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 22/07/2017.
//  Copyright 2017 Thomas Harte. All rights reserved.
//

#ifndef ClockReceiver_hpp
#define ClockReceiver_hpp

#include "ForceInline.hpp"
#include <cstdint>

/*
	Informal pattern for all classes that run from a clock cycle:

		Each will implement either or both of run_for(Cycles) and run_for(HalfCycles), as
		is appropriate.

		Callers that are accumulating HalfCycles but want to talk to receivers that implement
		only run_for(Cycles) can use HalfCycle.flush_cycles if they have appropriate storage, or
		can wrap the receiver in HalfClockReceiver in order automatically to bind half-cycle
		storage to it.

	Alignment rule:

		run_for(Cycles) may be called only after an even number of half cycles. E.g. the following
		sequence will have undefined results:

			run_for(HalfCycles(1))
			run_for(Cycles(1))

		An easy way to ensure this as a caller is to pick only one of run_for(Cycles) and
		run_for(HalfCycles) to use.

	Reasoning:

		Users of this template may with to implement run_for(Cycles) and run_for(HalfCycles)
		where there is a need to implement at half-cycle precision but a faster execution
		path can be offered for full-cycle precision. Those users are permitted to assume
		phase in run_for(Cycles) and should do so to be compatible with callers that use
		only run_for(Cycles).

	Corollary:

		Starting from nothing, the first run_for(HalfCycles(1)) will do the **first** half
		of a full cycle. The second will do the second half. Etc.

*/

/*!
	Provides a class that wraps a plain int, providing most of the basic arithmetic and
	Boolean operators, but forcing callers and receivers to be explicit as to usage.
*/
template <class T> class WrappedInt {
	public:
		using IntType = int64_t;

		forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {}
		forceinline constexpr WrappedInt() noexcept : length_(0) {}

		forceinline T &operator =(const T &rhs) {
			length_ = rhs.length_;
			return *this;
		}

		forceinline T &operator +=(const T &rhs) {
			length_ += rhs.length_;
			return *static_cast<T *>(this);
		}

		forceinline T &operator -=(const T &rhs) {
			length_ -= rhs.length_;
			return *static_cast<T *>(this);
		}

		forceinline T &operator ++() {
			++ length_;
			return *static_cast<T *>(this);
		}

		forceinline T &operator ++(int) {
			length_ ++;
			return *static_cast<T *>(this);
		}

		forceinline T &operator --() {
			-- length_;
			return *static_cast<T *>(this);
		}

		forceinline T &operator --(int) {
			length_ --;
			return *static_cast<T *>(this);
		}

		forceinline T &operator *=(const T &rhs) {
			length_ *= rhs.length_;
			return *static_cast<T *>(this);
		}

		forceinline T &operator /=(const T &rhs) {
			length_ /= rhs.length_;
			return *static_cast<T *>(this);
		}

		forceinline T &operator %=(const T &rhs) {
			length_ %= rhs.length_;
			return *static_cast<T *>(this);
		}

		forceinline T &operator &=(const T &rhs) {
			length_ &= rhs.length_;
			return *static_cast<T *>(this);
		}

		forceinline constexpr T operator +(const T &rhs) const			{	return T(length_ + rhs.length_);	}
		forceinline constexpr T operator -(const T &rhs) const			{	return T(length_ - rhs.length_);	}

		forceinline constexpr T operator *(const T &rhs) const			{	return T(length_ * rhs.length_);	}
		forceinline constexpr T operator /(const T &rhs) const			{	return T(length_ / rhs.length_);	}

		forceinline constexpr T operator %(const T &rhs) const			{	return T(length_ % rhs.length_);	}
		forceinline constexpr T operator &(const T &rhs) const			{	return T(length_ & rhs.length_);	}

		forceinline constexpr T operator -() const						{	return T(- length_);				}

		forceinline constexpr bool operator <(const T &rhs) const		{	return length_ < rhs.length_;		}
		forceinline constexpr bool operator >(const T &rhs) const		{	return length_ > rhs.length_;		}
		forceinline constexpr bool operator <=(const T &rhs) const		{	return length_ <= rhs.length_;		}
		forceinline constexpr bool operator >=(const T &rhs) const		{	return length_ >= rhs.length_;		}
		forceinline constexpr bool operator ==(const T &rhs) const		{	return length_ == rhs.length_;		}
		forceinline constexpr bool operator !=(const T &rhs) const		{	return length_ != rhs.length_;		}

		forceinline constexpr bool operator !() const					{	return !length_;					}
		// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse

		/// @returns The underlying int, cast to an integral type of your choosing.
		template<typename Type = IntType> forceinline constexpr Type as() { return Type(length_); }

		/// @returns The underlying int, in its native form.
		forceinline constexpr IntType as_integral() const { return length_; }

		/*!
			Severs from @c this the effect of dividing by @c divisor; @c this will end up with
			the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
		*/
		template <typename Result = T> forceinline Result divide(const T &divisor) {
			Result r;
			static_cast<T *>(this)->fill(r, divisor);
			return r;
		}

		/*!
			Flushes the value in @c this. The current value is returned, and the internal value
			is reset to zero.
		*/
		template <typename Result> Result flush() {
			// Jiggery pokery here; switching to function overloading avoids
			// the namespace-level requirement for template specialisation.
			Result r;
			static_cast<T *>(this)->fill(r);
			return r;
		}

		// operator int() is deliberately not provided, to avoid accidental subtitution of
		// classes that use this template.

	protected:
		IntType length_;
};

/// Describes an integer number of whole cycles: pairs of clock signal transitions.
class Cycles: public WrappedInt<Cycles> {
	public:
		forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
		forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
		forceinline constexpr Cycles(const Cycles &cycles) noexcept : WrappedInt<Cycles>(cycles.length_) {}

	private:
		friend WrappedInt;
		void fill(Cycles &result) {
			result.length_ = length_;
			length_ = 0;
		}

		void fill(Cycles &result, const Cycles &divisor) {
			result.length_ = length_ / divisor.length_;
			length_ %= divisor.length_;
		}
};

/// Describes an integer number of half cycles: single clock signal transitions.
class HalfCycles: public WrappedInt<HalfCycles> {
	public:
		forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
		forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}

		forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
		forceinline constexpr HalfCycles(const HalfCycles &half_cycles) noexcept : WrappedInt<HalfCycles>(half_cycles.length_) {}

		/// @returns The number of whole cycles completely covered by this span of half cycles.
		forceinline constexpr Cycles cycles() const {
			return Cycles(length_ >> 1);
		}

		/*!
			Severs from @c this the effect of dividing by @c divisor; @c this will end up with
			the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
		*/
		forceinline Cycles divide_cycles(const Cycles &divisor) {
			const HalfCycles half_divisor = HalfCycles(divisor);
			const Cycles result(length_ / half_divisor.length_);
			length_ %= half_divisor.length_;
			return result;
		}

	private:
		friend WrappedInt;
		void fill(Cycles &result) {
			result = Cycles(length_ >> 1);
			length_ &= 1;
		}

		void fill(HalfCycles &result) {
			result.length_ = length_;
			length_ = 0;
		}

		void fill(Cycles &result, const HalfCycles &divisor) {
			result = Cycles(length_ / (divisor.length_ << 1));
			length_ %= (divisor.length_ << 1);
		}

		void fill(HalfCycles &result, const HalfCycles &divisor) {
			result.length_ = length_ / divisor.length_;
			length_ %= divisor.length_;
		}
};

// Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles
// without losing the fractional part.

/*!
	If a component implements only run_for(Cycles), an owner can wrap it in HalfClockReceiver
	automatically to gain run_for(HalfCycles).
*/
template <class T> class HalfClockReceiver: public T {
	public:
		using T::T;

		forceinline void run_for(const HalfCycles half_cycles) {
			half_cycles_ += half_cycles;
			T::run_for(half_cycles_.flush<Cycles>());
		}

	private:
		HalfCycles half_cycles_;
};

#endif /* ClockReceiver_hpp */
<commit_msg>Corrects lack of `const`.<commit_after>//
//  ClockReceiver.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 22/07/2017.
//  Copyright 2017 Thomas Harte. All rights reserved.
//

#ifndef ClockReceiver_hpp
#define ClockReceiver_hpp

#include "ForceInline.hpp"
#include <cstdint>

/*
	Informal pattern for all classes that run from a clock cycle:

		Each will implement either or both of run_for(Cycles) and run_for(HalfCycles), as
		is appropriate.

		Callers that are accumulating HalfCycles but want to talk to receivers that implement
		only run_for(Cycles) can use HalfCycle.flush_cycles if they have appropriate storage, or
		can wrap the receiver in HalfClockReceiver in order automatically to bind half-cycle
		storage to it.

	Alignment rule:

		run_for(Cycles) may be called only after an even number of half cycles. E.g. the following
		sequence will have undefined results:

			run_for(HalfCycles(1))
			run_for(Cycles(1))

		An easy way to ensure this as a caller is to pick only one of run_for(Cycles) and
		run_for(HalfCycles) to use.

	Reasoning:

		Users of this template may with to implement run_for(Cycles) and run_for(HalfCycles)
		where there is a need to implement at half-cycle precision but a faster execution
		path can be offered for full-cycle precision. Those users are permitted to assume
		phase in run_for(Cycles) and should do so to be compatible with callers that use
		only run_for(Cycles).

	Corollary:

		Starting from nothing, the first run_for(HalfCycles(1)) will do the **first** half
		of a full cycle. The second will do the second half. Etc.

*/

/*!
	Provides a class that wraps a plain int, providing most of the basic arithmetic and
	Boolean operators, but forcing callers and receivers to be explicit as to usage.
*/
template <class T> class WrappedInt {
	public:
		using IntType = int64_t;

		forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {}
		forceinline constexpr WrappedInt() noexcept : length_(0) {}

		forceinline T &operator =(const T &rhs) {
			length_ = rhs.length_;
			return *this;
		}

		forceinline T &operator +=(const T &rhs) {
			length_ += rhs.length_;
			return *static_cast<T *>(this);
		}

		forceinline T &operator -=(const T &rhs) {
			length_ -= rhs.length_;
			return *static_cast<T *>(this);
		}

		forceinline T &operator ++() {
			++ length_;
			return *static_cast<T *>(this);
		}

		forceinline T &operator ++(int) {
			length_ ++;
			return *static_cast<T *>(this);
		}

		forceinline T &operator --() {
			-- length_;
			return *static_cast<T *>(this);
		}

		forceinline T &operator --(int) {
			length_ --;
			return *static_cast<T *>(this);
		}

		forceinline T &operator *=(const T &rhs) {
			length_ *= rhs.length_;
			return *static_cast<T *>(this);
		}

		forceinline T &operator /=(const T &rhs) {
			length_ /= rhs.length_;
			return *static_cast<T *>(this);
		}

		forceinline T &operator %=(const T &rhs) {
			length_ %= rhs.length_;
			return *static_cast<T *>(this);
		}

		forceinline T &operator &=(const T &rhs) {
			length_ &= rhs.length_;
			return *static_cast<T *>(this);
		}

		forceinline constexpr T operator +(const T &rhs) const			{	return T(length_ + rhs.length_);	}
		forceinline constexpr T operator -(const T &rhs) const			{	return T(length_ - rhs.length_);	}

		forceinline constexpr T operator *(const T &rhs) const			{	return T(length_ * rhs.length_);	}
		forceinline constexpr T operator /(const T &rhs) const			{	return T(length_ / rhs.length_);	}

		forceinline constexpr T operator %(const T &rhs) const			{	return T(length_ % rhs.length_);	}
		forceinline constexpr T operator &(const T &rhs) const			{	return T(length_ & rhs.length_);	}

		forceinline constexpr T operator -() const						{	return T(- length_);				}

		forceinline constexpr bool operator <(const T &rhs) const		{	return length_ < rhs.length_;		}
		forceinline constexpr bool operator >(const T &rhs) const		{	return length_ > rhs.length_;		}
		forceinline constexpr bool operator <=(const T &rhs) const		{	return length_ <= rhs.length_;		}
		forceinline constexpr bool operator >=(const T &rhs) const		{	return length_ >= rhs.length_;		}
		forceinline constexpr bool operator ==(const T &rhs) const		{	return length_ == rhs.length_;		}
		forceinline constexpr bool operator !=(const T &rhs) const		{	return length_ != rhs.length_;		}

		forceinline constexpr bool operator !() const					{	return !length_;					}
		// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse

		/// @returns The underlying int, cast to an integral type of your choosing.
		template<typename Type = IntType> forceinline constexpr Type as() const { return Type(length_); }

		/// @returns The underlying int, in its native form.
		forceinline constexpr IntType as_integral() const { return length_; }

		/*!
			Severs from @c this the effect of dividing by @c divisor; @c this will end up with
			the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
		*/
		template <typename Result = T> forceinline Result divide(const T &divisor) {
			Result r;
			static_cast<T *>(this)->fill(r, divisor);
			return r;
		}

		/*!
			Flushes the value in @c this. The current value is returned, and the internal value
			is reset to zero.
		*/
		template <typename Result> Result flush() {
			// Jiggery pokery here; switching to function overloading avoids
			// the namespace-level requirement for template specialisation.
			Result r;
			static_cast<T *>(this)->fill(r);
			return r;
		}

		// operator int() is deliberately not provided, to avoid accidental subtitution of
		// classes that use this template.

	protected:
		IntType length_;
};

/// Describes an integer number of whole cycles: pairs of clock signal transitions.
class Cycles: public WrappedInt<Cycles> {
	public:
		forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
		forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
		forceinline constexpr Cycles(const Cycles &cycles) noexcept : WrappedInt<Cycles>(cycles.length_) {}

	private:
		friend WrappedInt;
		void fill(Cycles &result) {
			result.length_ = length_;
			length_ = 0;
		}

		void fill(Cycles &result, const Cycles &divisor) {
			result.length_ = length_ / divisor.length_;
			length_ %= divisor.length_;
		}
};

/// Describes an integer number of half cycles: single clock signal transitions.
class HalfCycles: public WrappedInt<HalfCycles> {
	public:
		forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
		forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}

		forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
		forceinline constexpr HalfCycles(const HalfCycles &half_cycles) noexcept : WrappedInt<HalfCycles>(half_cycles.length_) {}

		/// @returns The number of whole cycles completely covered by this span of half cycles.
		forceinline constexpr Cycles cycles() const {
			return Cycles(length_ >> 1);
		}

		/*!
			Severs from @c this the effect of dividing by @c divisor; @c this will end up with
			the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
		*/
		forceinline Cycles divide_cycles(const Cycles &divisor) {
			const HalfCycles half_divisor = HalfCycles(divisor);
			const Cycles result(length_ / half_divisor.length_);
			length_ %= half_divisor.length_;
			return result;
		}

	private:
		friend WrappedInt;
		void fill(Cycles &result) {
			result = Cycles(length_ >> 1);
			length_ &= 1;
		}

		void fill(HalfCycles &result) {
			result.length_ = length_;
			length_ = 0;
		}

		void fill(Cycles &result, const HalfCycles &divisor) {
			result = Cycles(length_ / (divisor.length_ << 1));
			length_ %= (divisor.length_ << 1);
		}

		void fill(HalfCycles &result, const HalfCycles &divisor) {
			result.length_ = length_ / divisor.length_;
			length_ %= divisor.length_;
		}
};

// Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles
// without losing the fractional part.

/*!
	If a component implements only run_for(Cycles), an owner can wrap it in HalfClockReceiver
	automatically to gain run_for(HalfCycles).
*/
template <class T> class HalfClockReceiver: public T {
	public:
		using T::T;

		forceinline void run_for(const HalfCycles half_cycles) {
			half_cycles_ += half_cycles;
			T::run_for(half_cycles_.flush<Cycles>());
		}

	private:
		HalfCycles half_cycles_;
};

#endif /* ClockReceiver_hpp */
<|endoftext|>


<commit_before>//
// Copyright Copyright 2009-2021, AMT – The Association For Manufacturing Technology (“AMT”)
// All rights reserved.
//
//    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.
//

// Ensure that gtest is the first header otherwise Windows raises an error
#include <gtest/gtest.h>
// Keep this comment to keep gtest.h above. (clang-format off/on is not working here!)

#include "agent_test_helper.hpp"
#include "pipeline/shdr_token_mapper.hpp"
#include "pipeline/duplicate_filter.hpp"
#include "pipeline/delta_filter.hpp"
#include "pipeline/deliver.hpp"
#include "pipeline/pipeline.hpp"
#include "observation/observation.hpp"
#include "adapter/adapter.hpp"
#include <chrono>

using namespace mtconnect;
using namespace mtconnect::adapter;
using namespace mtconnect::pipeline;
using namespace mtconnect::observation;
using namespace std;
using namespace std::literals;
using namespace std::chrono_literals;

class PipelineDeliverTest : public testing::Test
{
 protected:
  void SetUp() override
  {  // Create an agent with only 16 slots and 8 data items.
    m_agentTestHelper = make_unique<AgentTestHelper>();
    m_agentTestHelper->createAgent("/samples/SimpleDevlce.xml",
                                   8, 4, "1.7", 25);
    m_agentId = to_string(getCurrentTimeInSec());
    m_device = m_agentTestHelper->m_agent->getDeviceByName("LinuxCNC");
  }

  void TearDown() override
  {
    m_agentTestHelper.reset();
  }

  std::unique_ptr<AgentTestHelper> m_agentTestHelper;
  std::string m_agentId;
  Device *m_device{nullptr};
};

TEST_F(PipelineDeliverTest, test_simple_flow)
{
  m_agentTestHelper->addAdapter();
  auto seq = m_agentTestHelper->m_agent->getSequence();
  m_agentTestHelper->m_adapter->processData("2021-01-22T12:33:45.123Z|Xpos|100.0");
  ASSERT_EQ(seq + 1, m_agentTestHelper->m_agent->getSequence());
  auto obs = m_agentTestHelper->m_agent->getFromBuffer(seq);
  ASSERT_TRUE(obs);
  ASSERT_EQ("Xpos", obs->getDataItem()->getName());
  ASSERT_EQ(100.0, obs->getValue<double>());
  ASSERT_EQ("2021-01-22T12:33:45.123Z", format(obs->getTimestamp()));
}

TEST_F(PipelineDeliverTest, filter_duplicates)
{
  ConfigOptions options{{configuration::FilterDuplicates, true}};
  m_agentTestHelper->addAdapter(options);
  auto seq = m_agentTestHelper->m_agent->getSequence();
  m_agentTestHelper->m_adapter->processData("2021-01-22T12:33:45.123Z|Xpos|100.0");
  ASSERT_EQ(seq + 1, m_agentTestHelper->m_agent->getSequence());

  auto obs = m_agentTestHelper->m_agent->getFromBuffer(seq);
  ASSERT_TRUE(obs);
  ASSERT_EQ("Xpos", obs->getDataItem()->getName());
  ASSERT_EQ(100.0, obs->getValue<double>());

  m_agentTestHelper->m_adapter->processData("2021-01-22T12:33:45.123Z|Xpos|100.0");
  ASSERT_EQ(seq + 1, m_agentTestHelper->m_agent->getSequence());

  m_agentTestHelper->m_adapter->processData("2021-01-22T12:33:45.123Z|Xpos|101.0");
  ASSERT_EQ(seq + 2, m_agentTestHelper->m_agent->getSequence());
  auto obs2 = m_agentTestHelper->m_agent->getFromBuffer(seq + 1);
  ASSERT_EQ(101.0, obs2->getValue<double>());
}

//a01c7f30
TEST_F(PipelineDeliverTest, filter_upcase)
{
  ConfigOptions options{{configuration::UpcaseDataItemValue, true}};
  m_agentTestHelper->addAdapter(options);
  auto seq = m_agentTestHelper->m_agent->getSequence();
  m_agentTestHelper->m_adapter->processData("2021-01-22T12:33:45.123Z|a01c7f30|active");
  ASSERT_EQ(seq + 1, m_agentTestHelper->m_agent->getSequence());

  auto obs = m_agentTestHelper->m_agent->getFromBuffer(seq);
  ASSERT_TRUE(obs);
  ASSERT_EQ("a01c7f30", obs->getDataItem()->getId());
  ASSERT_EQ("ACTIVE", obs->getValue<string>());

  m_agentTestHelper->m_adapter->processData("2021-01-22T12:33:45.123Z|Xpos|101.0");
  ASSERT_EQ(seq + 2, m_agentTestHelper->m_agent->getSequence());
  auto obs2 = m_agentTestHelper->m_agent->getFromBuffer(seq + 1);
  ASSERT_EQ(101.0, obs2->getValue<double>());
}

<commit_msg>Added tests for unit conversion and sample duration<commit_after>//
// Copyright Copyright 2009-2021, AMT – The Association For Manufacturing Technology (“AMT”)
// All rights reserved.
//
//    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.
//

// Ensure that gtest is the first header otherwise Windows raises an error
#include <gtest/gtest.h>
// Keep this comment to keep gtest.h above. (clang-format off/on is not working here!)

#include "agent_test_helper.hpp"
#include "pipeline/shdr_token_mapper.hpp"
#include "pipeline/duplicate_filter.hpp"
#include "pipeline/delta_filter.hpp"
#include "pipeline/deliver.hpp"
#include "pipeline/pipeline.hpp"
#include "observation/observation.hpp"
#include "adapter/adapter.hpp"
#include <chrono>

using namespace mtconnect;
using namespace mtconnect::adapter;
using namespace mtconnect::pipeline;
using namespace mtconnect::observation;
using namespace std;
using namespace std::literals;
using namespace std::chrono_literals;

class PipelineDeliverTest : public testing::Test
{
 protected:
  void SetUp() override
  {  // Create an agent with only 16 slots and 8 data items.
    m_agentTestHelper = make_unique<AgentTestHelper>();
    m_agentTestHelper->createAgent("/samples/SimpleDevlce.xml",
                                   8, 4, "1.7", 25);
    m_agentId = to_string(getCurrentTimeInSec());
    m_device = m_agentTestHelper->m_agent->getDeviceByName("LinuxCNC");
  }

  void TearDown() override
  {
    m_agentTestHelper.reset();
  }

  std::unique_ptr<AgentTestHelper> m_agentTestHelper;
  std::string m_agentId;
  Device *m_device{nullptr};
};

TEST_F(PipelineDeliverTest, test_simple_flow)
{
  m_agentTestHelper->addAdapter();
  auto seq = m_agentTestHelper->m_agent->getSequence();
  m_agentTestHelper->m_adapter->processData("2021-01-22T12:33:45.123Z|Xpos|100.0");
  ASSERT_EQ(seq + 1, m_agentTestHelper->m_agent->getSequence());
  auto obs = m_agentTestHelper->m_agent->getFromBuffer(seq);
  ASSERT_TRUE(obs);
  ASSERT_EQ("Xpos", obs->getDataItem()->getName());
  ASSERT_EQ(100.0, obs->getValue<double>());
  ASSERT_EQ("2021-01-22T12:33:45.123Z", format(obs->getTimestamp()));
}

TEST_F(PipelineDeliverTest, filter_duplicates)
{
  ConfigOptions options{{configuration::FilterDuplicates, true}};
  m_agentTestHelper->addAdapter(options);
  auto seq = m_agentTestHelper->m_agent->getSequence();
  m_agentTestHelper->m_adapter->processData("2021-01-22T12:33:45.123Z|Xpos|100.0");
  ASSERT_EQ(seq + 1, m_agentTestHelper->m_agent->getSequence());

  auto obs = m_agentTestHelper->m_agent->getFromBuffer(seq);
  ASSERT_TRUE(obs);
  ASSERT_EQ("Xpos", obs->getDataItem()->getName());
  ASSERT_EQ(100.0, obs->getValue<double>());

  m_agentTestHelper->m_adapter->processData("2021-01-22T12:33:45.123Z|Xpos|100.0");
  ASSERT_EQ(seq + 1, m_agentTestHelper->m_agent->getSequence());

  m_agentTestHelper->m_adapter->processData("2021-01-22T12:33:45.123Z|Xpos|101.0");
  ASSERT_EQ(seq + 2, m_agentTestHelper->m_agent->getSequence());
  auto obs2 = m_agentTestHelper->m_agent->getFromBuffer(seq + 1);
  ASSERT_EQ(101.0, obs2->getValue<double>());
}

//a01c7f30
TEST_F(PipelineDeliverTest, filter_upcase)
{
  ConfigOptions options{{configuration::UpcaseDataItemValue, true}};
  m_agentTestHelper->addAdapter(options);
  auto seq = m_agentTestHelper->m_agent->getSequence();
  m_agentTestHelper->m_adapter->processData("2021-01-22T12:33:45.123Z|a01c7f30|active");
  ASSERT_EQ(seq + 1, m_agentTestHelper->m_agent->getSequence());

  auto obs = m_agentTestHelper->m_agent->getFromBuffer(seq);
  ASSERT_TRUE(obs);
  ASSERT_EQ("a01c7f30", obs->getDataItem()->getId());
  ASSERT_EQ("ACTIVE", obs->getValue<string>());

  m_agentTestHelper->m_adapter->processData("2021-01-22T12:33:45.123Z|Xpos|101.0");
  ASSERT_EQ(seq + 2, m_agentTestHelper->m_agent->getSequence());
  auto obs2 = m_agentTestHelper->m_agent->getFromBuffer(seq + 1);
  ASSERT_EQ(101.0, obs2->getValue<double>());
}

TEST_F(PipelineDeliverTest, extract_duration)
{
  m_agentTestHelper->addAdapter();
  auto seq = m_agentTestHelper->m_agent->getSequence();
  m_agentTestHelper->m_adapter->processData("2021-01-22T12:33:45.123Z@200.1232|Xpos|100.0");
  ASSERT_EQ(seq + 1, m_agentTestHelper->m_agent->getSequence());
  auto obs = m_agentTestHelper->m_agent->getFromBuffer(seq);
  ASSERT_TRUE(obs);
  ASSERT_EQ("Xpos", obs->getDataItem()->getName());
  ASSERT_EQ(100.0, obs->getValue<double>());
  ASSERT_EQ("2021-01-22T12:33:45.123Z", format(obs->getTimestamp()));
  ASSERT_EQ(200.1232, obs->get<double>("duration"));
}

TEST_F(PipelineDeliverTest, unit_conversion)
{
  m_agentTestHelper->addAdapter(ConfigOptions{{configuration::ConversionRequired, true}});
  auto seq = m_agentTestHelper->m_agent->getSequence();
  m_agentTestHelper->m_adapter->processData("2021-01-22T12:33:48.123Z|amp|10.1");
  ASSERT_EQ(seq + 1, m_agentTestHelper->m_agent->getSequence());
  auto obs = m_agentTestHelper->m_agent->getFromBuffer(seq);
  ASSERT_TRUE(obs);
  ASSERT_EQ("amp", obs->getDataItem()->getName());
  ASSERT_EQ("KILOAMPERE", obs->getDataItem()->getNativeUnits());
  std::cout << obs->get<double>("VALUE");
  ASSERT_EQ(10100.0, obs->getValue<double>());
}<|endoftext|>


<commit_before>/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

#include "osl/module.h"
#include "osl/process.h"

#include "rtl/ustrbuf.hxx"

#include "salinst.hxx"
#include "generic/gensys.h"
#include "generic/gendata.hxx"
#include "unx/desktops.hxx"
#include "vcl/printerinfomanager.hxx"

#include <cstdio>
#include <unistd.h>

using ::rtl::OUString;
using ::rtl::OUStringBuffer;
extern "C" {
typedef SalInstance*(*salFactoryProc)( oslModule pModule);
}

static oslModule pCloseModule = NULL;

static SalInstance* tryInstance( const OUString& rModuleBase )
{
    SalInstance* pInst = NULL;

    // Disable gtk3 plugin load except in experimental mode for now.
    if( rModuleBase.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "gtk3" ) ) &&
        !SalGenericSystem::enableExperimentalFeatures() )
        return NULL;

    OUStringBuffer aModName( 128 );
    aModName.appendAscii( SAL_DLLPREFIX"vclplug_" );
    aModName.append( rModuleBase );
    aModName.appendAscii( SAL_DLLPOSTFIX );
    OUString aModule = aModName.makeStringAndClear();

    oslModule aMod = osl_loadModuleRelative(
        reinterpret_cast< oslGenericFunction >( &tryInstance ), aModule.pData,
        SAL_LOADMODULE_DEFAULT );
    if( aMod )
    {
        salFactoryProc aProc = (salFactoryProc)osl_getAsciiFunctionSymbol( aMod, "create_SalInstance" );
        if( aProc )
        {
            pInst = aProc( aMod );
#if OSL_DEBUG_LEVEL > 1
            std::fprintf( stderr, "sal plugin %s produced instance %p\n",
                     OUStringToOString( aModule, RTL_TEXTENCODING_ASCII_US ).getStr(),
                     pInst );
#endif
            if( pInst )
            {
                pCloseModule = aMod;

#ifndef ANDROID
                /*
                 * Recent GTK+ versions load their modules with RTLD_LOCAL, so we can
                 * not access the 'gnome_accessibility_module_shutdown' anymore.
                 * So make sure libgtk+ & co are still mapped into memory when
                 * atk-bridge's atexit handler gets called.
                 */
                if( rModuleBase.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("gtk")) ||
                    rModuleBase.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("gtk3")) )
                {
                    pCloseModule = NULL;
                }
                /*
                 * #i109007# KDE3 seems to have the same problem; an atexit cleanup
                 * handler, which cannot be resolved anymore if the plugin is already unloaded.
                 */
                else if( rModuleBase.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("kde")) )
                {
                    pCloseModule = NULL;
                }
#endif
                GetSalData()->m_pPlugin = aMod;
            }
            else
                osl_unloadModule( aMod );
        }
        else
        {
#if OSL_DEBUG_LEVEL > 1
            std::fprintf( stderr, "could not load symbol %s from shared object %s\n",
                     "create_SalInstance",
                     OUStringToOString( aModule, RTL_TEXTENCODING_ASCII_US ).getStr() );
#endif
            osl_unloadModule( aMod );
        }
    }
#if OSL_DEBUG_LEVEL > 1
    else
        std::fprintf( stderr, "could not load shared object %s\n",
                 OUStringToOString( aModule, RTL_TEXTENCODING_ASCII_US ).getStr() );
#endif

    return pInst;
}

#ifndef ANDROID

static DesktopType get_desktop_environment()
{
    OUStringBuffer aModName( 128 );
    aModName.appendAscii( SAL_DLLPREFIX"desktop_detector" );
    aModName.appendAscii( SAL_DLLPOSTFIX );
    OUString aModule = aModName.makeStringAndClear();

    oslModule aMod = osl_loadModuleRelative(
        reinterpret_cast< oslGenericFunction >( &tryInstance ), aModule.pData,
        SAL_LOADMODULE_DEFAULT );
    DesktopType ret = DESKTOP_UNKNOWN;
    if( aMod )
    {
        DesktopType (*pSym)() = (DesktopType(*)())
            osl_getAsciiFunctionSymbol( aMod, "get_desktop_environment" );
        if( pSym )
            ret = pSym();
    }
    osl_unloadModule( aMod );
    return ret;
}

#else

#define get_desktop_environment() DESKTOP_NONE // For now...

#endif

static SalInstance* autodetect_plugin()
{
    static const char* pKDEFallbackList[] =
    {
        "kde4", "kde", "gtk3", "gtk", "gen", 0
    };

    static const char* pStandardFallbackList[] =
    {
        "gtk3", "gtk", "gen", 0
    };

    static const char* pHeadlessFallbackList[] =
    {
        "svp", 0
    };

    DesktopType desktop = get_desktop_environment();
    const char ** pList = pStandardFallbackList;
    int nListEntry = 0;

    // no server at all: dummy plugin
    if ( desktop == DESKTOP_NONE )
        pList = pHeadlessFallbackList;
    else if ( desktop == DESKTOP_GNOME )
        pList = pStandardFallbackList;
    else if( desktop == DESKTOP_KDE )
    {
        pList = pKDEFallbackList;
        nListEntry = 1;
    }
    else if( desktop == DESKTOP_KDE4 )
        pList = pKDEFallbackList;

    SalInstance* pInst = NULL;
    while( pList[nListEntry] && pInst == NULL )
    {
        rtl::OUString aTry( rtl::OUString::createFromAscii( pList[nListEntry] ) );
        pInst = tryInstance( aTry );
        #if OSL_DEBUG_LEVEL > 1
        if( pInst )
            std::fprintf( stderr, "plugin autodetection: %s\n", pList[nListEntry] );
        #endif
        nListEntry++;
    }

    return pInst;
}

static SalInstance* check_headless_plugin()
{
    int nParams = osl_getCommandArgCount();
    OUString aParam;
    for( int i = 0; i < nParams; i++ )
    {
        osl_getCommandArg( i, &aParam.pData );
        if( aParam.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("-headless")) ||
            aParam.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("--headless")) )
        {
            return tryInstance( OUString( RTL_CONSTASCII_USTRINGPARAM( "svp" ) ) );
        }
    }
    return NULL;
}

SalInstance *CreateSalInstance()
{
    SalInstance*    pInst = NULL;

    static const char* pUsePlugin = getenv( "SAL_USE_VCLPLUGIN" );

    pInst = check_headless_plugin();

    if( !pInst && pUsePlugin && *pUsePlugin )
        pInst = tryInstance( OUString::createFromAscii( pUsePlugin ) );

    if( ! pInst )
        pInst = autodetect_plugin();

    // fallback, try everything
    const char* pPlugin[] = { "gtk3", "gtk", "kde4", "kde", "gen", 0 };

    for ( int i = 0; !pInst && pPlugin[ i ]; ++i )
        pInst = tryInstance( OUString::createFromAscii( pPlugin[ i ] ) );

    if( ! pInst )
    {
        std::fprintf( stderr, "no suitable windowing system found, exiting.\n" );
        _exit( 1 );
    }

    // acquire SolarMutex
    pInst->AcquireYieldMutex( 1 );

    return pInst;
}

void DestroySalInstance( SalInstance *pInst )
{
    // release SolarMutex
    pInst->ReleaseYieldMutex();

    delete pInst;
    if( pCloseModule )
        osl_unloadModule( pCloseModule );
}

void InitSalData()
{
}

void DeInitSalData()
{
}

void InitSalMain()
{
}

void DeInitSalMain()
{
}

void SalAbort( const rtl::OUString& rErrorText, bool bDumpCore )
{
    if( rErrorText.isEmpty() )
        std::fprintf( stderr, "Application Error\n" );
    else
        std::fprintf( stderr, "%s\n", rtl::OUStringToOString(rErrorText, osl_getThreadTextEncoding()).getStr() );
    if( bDumpCore )
        abort();
    else
        _exit(1);
}

static const char * desktop_strings[] = { "none", "unknown", "GNOME", "KDE", "KDE4" };

const OUString& SalGetDesktopEnvironment()
{
    static rtl::OUString aRet;
    if( aRet.isEmpty())
    {
        rtl::OUStringBuffer buf( 8 );
        buf.appendAscii( desktop_strings[ get_desktop_environment() ] );
        aRet = buf.makeStringAndClear();
    }
    return aRet;
}

SalData::SalData() :
    m_pInstance(NULL),
    m_pPlugin(NULL),
    m_pPIManager(NULL)
{
}

SalData::~SalData()
{
    psp::PrinterInfoManager::release();
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
<commit_msg>The "generic" thing is X11-specific<commit_after>/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

#include "osl/module.h"
#include "osl/process.h"

#include "rtl/ustrbuf.hxx"

#include "salinst.hxx"
#include "generic/gensys.h"
#include "generic/gendata.hxx"
#include "unx/desktops.hxx"
#include "vcl/printerinfomanager.hxx"

#include <cstdio>
#include <unistd.h>

using ::rtl::OUString;
using ::rtl::OUStringBuffer;
extern "C" {
typedef SalInstance*(*salFactoryProc)( oslModule pModule);
}

static oslModule pCloseModule = NULL;

static SalInstance* tryInstance( const OUString& rModuleBase )
{
    SalInstance* pInst = NULL;
#ifndef ANDROID
    // Disable gtk3 plugin load except in experimental mode for now.
    if( rModuleBase.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "gtk3" ) ) &&
        !SalGenericSystem::enableExperimentalFeatures() )
        return NULL;
#endif
    OUStringBuffer aModName( 128 );
    aModName.appendAscii( SAL_DLLPREFIX"vclplug_" );
    aModName.append( rModuleBase );
    aModName.appendAscii( SAL_DLLPOSTFIX );
    OUString aModule = aModName.makeStringAndClear();

    oslModule aMod = osl_loadModuleRelative(
        reinterpret_cast< oslGenericFunction >( &tryInstance ), aModule.pData,
        SAL_LOADMODULE_DEFAULT );
    if( aMod )
    {
        salFactoryProc aProc = (salFactoryProc)osl_getAsciiFunctionSymbol( aMod, "create_SalInstance" );
        if( aProc )
        {
            pInst = aProc( aMod );
#if OSL_DEBUG_LEVEL > 1
            std::fprintf( stderr, "sal plugin %s produced instance %p\n",
                     OUStringToOString( aModule, RTL_TEXTENCODING_ASCII_US ).getStr(),
                     pInst );
#endif
            if( pInst )
            {
                pCloseModule = aMod;

#ifndef ANDROID
                /*
                 * Recent GTK+ versions load their modules with RTLD_LOCAL, so we can
                 * not access the 'gnome_accessibility_module_shutdown' anymore.
                 * So make sure libgtk+ & co are still mapped into memory when
                 * atk-bridge's atexit handler gets called.
                 */
                if( rModuleBase.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("gtk")) ||
                    rModuleBase.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("gtk3")) )
                {
                    pCloseModule = NULL;
                }
                /*
                 * #i109007# KDE3 seems to have the same problem; an atexit cleanup
                 * handler, which cannot be resolved anymore if the plugin is already unloaded.
                 */
                else if( rModuleBase.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("kde")) )
                {
                    pCloseModule = NULL;
                }
#endif
                GetSalData()->m_pPlugin = aMod;
            }
            else
                osl_unloadModule( aMod );
        }
        else
        {
#if OSL_DEBUG_LEVEL > 1
            std::fprintf( stderr, "could not load symbol %s from shared object %s\n",
                     "create_SalInstance",
                     OUStringToOString( aModule, RTL_TEXTENCODING_ASCII_US ).getStr() );
#endif
            osl_unloadModule( aMod );
        }
    }
#if OSL_DEBUG_LEVEL > 1
    else
        std::fprintf( stderr, "could not load shared object %s\n",
                 OUStringToOString( aModule, RTL_TEXTENCODING_ASCII_US ).getStr() );
#endif

    return pInst;
}

#ifndef ANDROID

static DesktopType get_desktop_environment()
{
    OUStringBuffer aModName( 128 );
    aModName.appendAscii( SAL_DLLPREFIX"desktop_detector" );
    aModName.appendAscii( SAL_DLLPOSTFIX );
    OUString aModule = aModName.makeStringAndClear();

    oslModule aMod = osl_loadModuleRelative(
        reinterpret_cast< oslGenericFunction >( &tryInstance ), aModule.pData,
        SAL_LOADMODULE_DEFAULT );
    DesktopType ret = DESKTOP_UNKNOWN;
    if( aMod )
    {
        DesktopType (*pSym)() = (DesktopType(*)())
            osl_getAsciiFunctionSymbol( aMod, "get_desktop_environment" );
        if( pSym )
            ret = pSym();
    }
    osl_unloadModule( aMod );
    return ret;
}

#else

#define get_desktop_environment() DESKTOP_NONE // For now...

#endif

static SalInstance* autodetect_plugin()
{
    static const char* pKDEFallbackList[] =
    {
        "kde4", "kde", "gtk3", "gtk", "gen", 0
    };

    static const char* pStandardFallbackList[] =
    {
        "gtk3", "gtk", "gen", 0
    };

    static const char* pHeadlessFallbackList[] =
    {
        "svp", 0
    };

    DesktopType desktop = get_desktop_environment();
    const char ** pList = pStandardFallbackList;
    int nListEntry = 0;

    // no server at all: dummy plugin
    if ( desktop == DESKTOP_NONE )
        pList = pHeadlessFallbackList;
    else if ( desktop == DESKTOP_GNOME )
        pList = pStandardFallbackList;
    else if( desktop == DESKTOP_KDE )
    {
        pList = pKDEFallbackList;
        nListEntry = 1;
    }
    else if( desktop == DESKTOP_KDE4 )
        pList = pKDEFallbackList;

    SalInstance* pInst = NULL;
    while( pList[nListEntry] && pInst == NULL )
    {
        rtl::OUString aTry( rtl::OUString::createFromAscii( pList[nListEntry] ) );
        pInst = tryInstance( aTry );
        #if OSL_DEBUG_LEVEL > 1
        if( pInst )
            std::fprintf( stderr, "plugin autodetection: %s\n", pList[nListEntry] );
        #endif
        nListEntry++;
    }

    return pInst;
}

static SalInstance* check_headless_plugin()
{
    int nParams = osl_getCommandArgCount();
    OUString aParam;
    for( int i = 0; i < nParams; i++ )
    {
        osl_getCommandArg( i, &aParam.pData );
        if( aParam.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("-headless")) ||
            aParam.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("--headless")) )
        {
            return tryInstance( OUString( RTL_CONSTASCII_USTRINGPARAM( "svp" ) ) );
        }
    }
    return NULL;
}

SalInstance *CreateSalInstance()
{
    SalInstance*    pInst = NULL;

    static const char* pUsePlugin = getenv( "SAL_USE_VCLPLUGIN" );

    pInst = check_headless_plugin();

    if( !pInst && pUsePlugin && *pUsePlugin )
        pInst = tryInstance( OUString::createFromAscii( pUsePlugin ) );

    if( ! pInst )
        pInst = autodetect_plugin();

    // fallback, try everything
    const char* pPlugin[] = { "gtk3", "gtk", "kde4", "kde", "gen", 0 };

    for ( int i = 0; !pInst && pPlugin[ i ]; ++i )
        pInst = tryInstance( OUString::createFromAscii( pPlugin[ i ] ) );

    if( ! pInst )
    {
        std::fprintf( stderr, "no suitable windowing system found, exiting.\n" );
        _exit( 1 );
    }

    // acquire SolarMutex
    pInst->AcquireYieldMutex( 1 );

    return pInst;
}

void DestroySalInstance( SalInstance *pInst )
{
    // release SolarMutex
    pInst->ReleaseYieldMutex();

    delete pInst;
    if( pCloseModule )
        osl_unloadModule( pCloseModule );
}

void InitSalData()
{
}

void DeInitSalData()
{
}

void InitSalMain()
{
}

void DeInitSalMain()
{
}

void SalAbort( const rtl::OUString& rErrorText, bool bDumpCore )
{
    if( rErrorText.isEmpty() )
        std::fprintf( stderr, "Application Error\n" );
    else
        std::fprintf( stderr, "%s\n", rtl::OUStringToOString(rErrorText, osl_getThreadTextEncoding()).getStr() );
    if( bDumpCore )
        abort();
    else
        _exit(1);
}

static const char * desktop_strings[] = { "none", "unknown", "GNOME", "KDE", "KDE4" };

const OUString& SalGetDesktopEnvironment()
{
    static rtl::OUString aRet;
    if( aRet.isEmpty())
    {
        rtl::OUStringBuffer buf( 8 );
        buf.appendAscii( desktop_strings[ get_desktop_environment() ] );
        aRet = buf.makeStringAndClear();
    }
    return aRet;
}

SalData::SalData() :
    m_pInstance(NULL),
    m_pPlugin(NULL),
    m_pPIManager(NULL)
{
}

SalData::~SalData()
{
    psp::PrinterInfoManager::release();
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
<|endoftext|>


<commit_before>#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE Main
#include "test_common.h"
#include <boost/test/unit_test.hpp>

#include <libdariadb/dariadb.h>
#include <libdariadb/storage/manifest.h>
#include <libdariadb/timeutil.h>
#include <libdariadb/utils/fs.h>
#include <algorithm>
#include <iostream>

class BenchCallback : public dariadb::IReadCallback {
public:
  BenchCallback() { count = 0; }
  void call(const dariadb::Meas &) { count++; }
  size_t count;
};

BOOST_AUTO_TEST_CASE(Engine_common_test) {
  const std::string storage_path = "testStorage";
  const size_t chunk_size = 256;

  const dariadb::Time from = 0;
  const dariadb::Time to = from + 1000;
  const dariadb::Time step = 10;
  using namespace dariadb;
  using namespace dariadb::storage;
  {
    std::cout << "Engine_common_test.\n";
    if (dariadb::utils::fs::path_exists(storage_path)) {
      dariadb::utils::fs::rm(storage_path);
    }

    auto settings = dariadb::storage::Settings::create(storage_path);
    settings->wal_cache_size.setValue(100);
    settings->wal_file_size.setValue(settings->wal_cache_size.value() * 5);
    settings->chunk_size.setValue(chunk_size);
    std::unique_ptr<Engine> ms{new Engine(settings)};

    dariadb_test::storage_test_check(ms.get(), from, to, step, true, true, false);

    auto pages_count = ms->description().pages_count;
    BOOST_CHECK_GE(pages_count, size_t(2));
    BOOST_CHECK(ms->settings() != nullptr);
    BOOST_CHECK(ms->settings()->storage_path.value() == storage_path);
  }
  {
    std::cout << "reopen closed storage\n";

	auto ms = dariadb::open_storage(storage_path);
	auto settings = ms->settings();

    auto index_files = dariadb::utils::fs::ls(settings->raw_path.value(), ".pagei");
    BOOST_CHECK(!index_files.empty());
    for (auto &f : index_files) {
      dariadb::utils::fs::rm(f);
    }
    index_files = dariadb::utils::fs::ls(settings->raw_path.value(), ".pagei");
    BOOST_CHECK(index_files.empty());

    ms->fsck();

    ms->wait_all_asyncs();

    // check first id, because that Id placed in compressed pages.
    auto values = ms->readInterval(QueryInterval({dariadb::Id(0)}, 0, from, to));
    BOOST_CHECK_EQUAL(values.size(), dariadb_test::copies_count);

    auto current = ms->currentValue(dariadb::IdArray{}, 0);
    BOOST_CHECK(current.size() != size_t(0));

    std::cout << "erase old files" << std::endl;
    ms->settings()->max_store_period.setValue(1);
    while (true) {
      index_files = dariadb::utils::fs::ls(settings->raw_path.value(), ".pagei");
      if (index_files.empty()) {
        break;
      }
      std::cout << "file left:" << std::endl;
      for (auto i : index_files) {
        std::cout << i << std::endl;
      }
      std::this_thread::sleep_for(std::chrono::seconds(1));
    }
  }
  std::cout << "end\n";
  if (dariadb::utils::fs::path_exists(storage_path)) {
    dariadb::utils::fs::rm(storage_path);
  }
}

BOOST_AUTO_TEST_CASE(Engine_compress_all_test) {
  const std::string storage_path = "testStorage";
  const size_t chunk_size = 256;

  const dariadb::Time from = 0;
  const dariadb::Time to = from + 50;
  const dariadb::Time step = 10;

  using namespace dariadb;
  using namespace dariadb::storage;

  {
    std::cout << "Engine_compress_all_test\n";
    if (dariadb::utils::fs::path_exists(storage_path)) {
      dariadb::utils::fs::rm(storage_path);
    }

    auto settings = dariadb::storage::Settings::create(storage_path);
    settings->wal_cache_size.setValue(100);
    settings->wal_file_size.setValue(settings->wal_cache_size.value() * 2);
    settings->chunk_size.setValue(chunk_size);
    settings->strategy.setValue(dariadb::STRATEGY::WAL);
    std::unique_ptr<Engine> ms{new Engine(settings)};

    dariadb::IdSet all_ids;
    dariadb::Time maxWritedTime;
    dariadb_test::fill_storage_for_test(ms.get(), from, to, step, &all_ids,
                                        &maxWritedTime, false);

    ms->compress_all();
    while (true) {
      auto wals_count = ms->description().wal_count;
      if (wals_count == 0) {
        break;
      }
      dariadb::utils::sleep_mls(500);
    }
  }
  if (dariadb::utils::fs::path_exists(storage_path)) {
    dariadb::utils::fs::rm(storage_path);
  }
}

BOOST_AUTO_TEST_CASE(Subscribe) {
  const std::string storage_path = "testStorage";

  {
    if (dariadb::utils::fs::path_exists(storage_path)) {
      dariadb::utils::fs::rm(storage_path);
    }

    auto settings = dariadb::storage::Settings::create(storage_path);

    dariadb::IEngine_Ptr ms = std::make_shared<dariadb::Engine>(settings);

    dariadb_test::subscribe_test(ms.get());
  }
  if (dariadb::utils::fs::path_exists(storage_path)) {
    dariadb::utils::fs::rm(storage_path);
  }
}

BOOST_AUTO_TEST_CASE(Engine_MemStorage_common_test) {
  const std::string storage_path = "testStorage";
  const size_t chunk_size = 128;

  const dariadb::Time from = 0;
  const dariadb::Time to = from + 1000;
  const dariadb::Time step = 10;

  using namespace dariadb;
  using namespace dariadb::storage;

  {
    std::cout << "Engine_MemStorage_common_test\n";
    if (dariadb::utils::fs::path_exists(storage_path)) {
      dariadb::utils::fs::rm(storage_path);
    }

    auto settings = dariadb::storage::Settings::create(storage_path);
    settings->strategy.setValue(STRATEGY::MEMORY);
    settings->chunk_size.setValue(chunk_size);
    settings->max_chunks_per_page.setValue(5);
    settings->memory_limit.setValue(50 * 1024);
    std::unique_ptr<Engine> ms{new Engine(settings)};

    dariadb_test::storage_test_check(ms.get(), from, to, step, true, false, false);

    auto pages_count = ms->description().pages_count;
    BOOST_CHECK_GE(pages_count, size_t(2));
    ms->settings()->max_store_period.setValue(1);
  }
  if (dariadb::utils::fs::path_exists(storage_path)) {
    dariadb::utils::fs::rm(storage_path);
  }
}

BOOST_AUTO_TEST_CASE(Engine_MemOnlyStorage_common_test) {
  const size_t chunk_size = 128;

  const dariadb::Time from = 0;
  const dariadb::Time to = from + 1000;
  const dariadb::Time step = 10;

  using namespace dariadb;
  using namespace dariadb::storage;

  {
    std::cout << "Engine_MemOnlyStorage_common_test\n";

    auto settings = dariadb::storage::Settings::create();
    settings->chunk_size.setValue(chunk_size);
    std::unique_ptr<Engine> ms{new Engine(settings)};

    dariadb_test::storage_test_check(ms.get(), from, to, step, true, false, false);

    auto pages_count = ms->description().pages_count;
    BOOST_CHECK_EQUAL(pages_count, size_t(0));
    ms->settings()->max_store_period.setValue(1);
    while (true) {
      dariadb::QueryInterval qi({dariadb::Id(0)}, dariadb::Flag(), from, to);
      auto values = ms->readInterval(qi);
      if (values.empty()) {
        break;
      } else {
        std::cout << "values !empty() " << values.size() << std::endl;
        dariadb::utils::sleep_mls(500);
      }
    }
  }
}

BOOST_AUTO_TEST_CASE(Engine_Cache_common_test) {
  const std::string storage_path = "testStorage";

  const size_t chunk_size = 128;
  const dariadb::Time from = 0;
  const dariadb::Time to = from + 1000;
  const dariadb::Time step = 10;

  using namespace dariadb;
  using namespace dariadb::storage;

  {
    std::cout << "Engine_Cache_common_test\n";
    if (dariadb::utils::fs::path_exists(storage_path)) {
      dariadb::utils::fs::rm(storage_path);
    }

    auto settings = dariadb::storage::Settings::create(storage_path);
    settings->strategy.setValue(STRATEGY::CACHE);
    settings->chunk_size.setValue(chunk_size);
    settings->memory_limit.setValue(50 * 1024);
    settings->wal_file_size.setValue(2000);
    std::unique_ptr<Engine> ms{new Engine(settings)};

    dariadb_test::storage_test_check(ms.get(), from, to, step, true, true, false);

    auto descr = ms->description();
    BOOST_CHECK_GT(descr.pages_count, size_t(0));
    ms->settings()->max_store_period.setValue(1);
  }
  if (dariadb::utils::fs::path_exists(storage_path)) {
    dariadb::utils::fs::rm(storage_path);
  }
}
<commit_msg>engine_test: refact.<commit_after>#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE Main
#include "test_common.h"
#include <boost/test/unit_test.hpp>

#include <libdariadb/dariadb.h>
#include <libdariadb/storage/manifest.h>
#include <libdariadb/timeutil.h>
#include <libdariadb/utils/fs.h>
#include <algorithm>
#include <iostream>

class BenchCallback : public dariadb::IReadCallback {
public:
  BenchCallback() { count = 0; }
  void call(const dariadb::Meas &) { count++; }
  size_t count;
};

BOOST_AUTO_TEST_CASE(Engine_common_test) {
  const std::string storage_path = "testStorage";
  const size_t chunk_size = 256;

  const dariadb::Time from = 0;
  const dariadb::Time to = from + 1000;
  const dariadb::Time step = 10;
  using namespace dariadb;
  using namespace dariadb::storage;
  {
    std::cout << "Engine_common_test.\n";
    if (dariadb::utils::fs::path_exists(storage_path)) {
      dariadb::utils::fs::rm(storage_path);
    }

    auto settings = dariadb::storage::Settings::create(storage_path);
    settings->wal_cache_size.setValue(100);
    settings->wal_file_size.setValue(settings->wal_cache_size.value() * 5);
    settings->chunk_size.setValue(chunk_size);
    std::unique_ptr<Engine> ms{new Engine(settings)};

    dariadb_test::storage_test_check(ms.get(), from, to, step, true, true, false);

    auto pages_count = ms->description().pages_count;
    BOOST_CHECK_GE(pages_count, size_t(2));
    BOOST_CHECK(ms->settings() != nullptr);
    BOOST_CHECK(ms->settings()->storage_path.value() == storage_path);
  }
  {
    std::cout << "reopen closed storage\n";
	{// bad idea remove files from working storage.
		auto ms = dariadb::open_storage(storage_path);
		auto settings = ms->settings();

		auto path_to_raw = settings->raw_path.value();
		settings = nullptr;
		ms->stop();

		ms = nullptr;
		auto index_files = dariadb::utils::fs::ls(path_to_raw, ".pagei");
		BOOST_CHECK(!index_files.empty());
		for (auto &f : index_files) {
			dariadb::utils::fs::rm(f);
		}
		index_files = dariadb::utils::fs::ls(path_to_raw, ".pagei");
		BOOST_CHECK(index_files.empty());
	}

	auto ms = dariadb::open_storage(storage_path);
	auto settings = ms->settings();

    ms->fsck();

    ms->wait_all_asyncs();

    // check first id, because that Id placed in compressed pages.
    auto values = ms->readInterval(QueryInterval({dariadb::Id(0)}, 0, from, to));
    BOOST_CHECK_EQUAL(values.size(), dariadb_test::copies_count);

    auto current = ms->currentValue(dariadb::IdArray{}, 0);
    BOOST_CHECK(current.size() != size_t(0));

    std::cout << "erase old files" << std::endl;
    ms->settings()->max_store_period.setValue(1);
    while (true) {
      auto index_files = dariadb::utils::fs::ls(settings->raw_path.value(), ".pagei");
      if (index_files.empty()) {
        break;
      }
      std::cout << "file left:" << std::endl;
      for (auto i : index_files) {
        std::cout << i << std::endl;
      }
      std::this_thread::sleep_for(std::chrono::seconds(1));
    }
  }
  std::cout << "end\n";
  if (dariadb::utils::fs::path_exists(storage_path)) {
    dariadb::utils::fs::rm(storage_path);
  }
}

BOOST_AUTO_TEST_CASE(Engine_compress_all_test) {
  const std::string storage_path = "testStorage";
  const size_t chunk_size = 256;

  const dariadb::Time from = 0;
  const dariadb::Time to = from + 50;
  const dariadb::Time step = 10;

  using namespace dariadb;
  using namespace dariadb::storage;

  {
    std::cout << "Engine_compress_all_test\n";
    if (dariadb::utils::fs::path_exists(storage_path)) {
      dariadb::utils::fs::rm(storage_path);
    }

    auto settings = dariadb::storage::Settings::create(storage_path);
    settings->wal_cache_size.setValue(100);
    settings->wal_file_size.setValue(settings->wal_cache_size.value() * 2);
    settings->chunk_size.setValue(chunk_size);
    settings->strategy.setValue(dariadb::STRATEGY::WAL);
    std::unique_ptr<Engine> ms{new Engine(settings)};

    dariadb::IdSet all_ids;
    dariadb::Time maxWritedTime;
    dariadb_test::fill_storage_for_test(ms.get(), from, to, step, &all_ids,
                                        &maxWritedTime, false);

    ms->compress_all();
    while (true) {
      auto wals_count = ms->description().wal_count;
      if (wals_count == 0) {
        break;
      }
      dariadb::utils::sleep_mls(500);
    }
  }
  if (dariadb::utils::fs::path_exists(storage_path)) {
    dariadb::utils::fs::rm(storage_path);
  }
}

BOOST_AUTO_TEST_CASE(Subscribe) {
  const std::string storage_path = "testStorage";

  {
    if (dariadb::utils::fs::path_exists(storage_path)) {
      dariadb::utils::fs::rm(storage_path);
    }

    auto settings = dariadb::storage::Settings::create(storage_path);

    dariadb::IEngine_Ptr ms = std::make_shared<dariadb::Engine>(settings);

    dariadb_test::subscribe_test(ms.get());
  }
  if (dariadb::utils::fs::path_exists(storage_path)) {
    dariadb::utils::fs::rm(storage_path);
  }
}

BOOST_AUTO_TEST_CASE(Engine_MemStorage_common_test) {
  const std::string storage_path = "testStorage";
  const size_t chunk_size = 128;

  const dariadb::Time from = 0;
  const dariadb::Time to = from + 1000;
  const dariadb::Time step = 10;

  using namespace dariadb;
  using namespace dariadb::storage;

  {
    std::cout << "Engine_MemStorage_common_test\n";
    if (dariadb::utils::fs::path_exists(storage_path)) {
      dariadb::utils::fs::rm(storage_path);
    }

    auto settings = dariadb::storage::Settings::create(storage_path);
    settings->strategy.setValue(STRATEGY::MEMORY);
    settings->chunk_size.setValue(chunk_size);
    settings->max_chunks_per_page.setValue(5);
    settings->memory_limit.setValue(50 * 1024);
    std::unique_ptr<Engine> ms{new Engine(settings)};

    dariadb_test::storage_test_check(ms.get(), from, to, step, true, false, false);

    auto pages_count = ms->description().pages_count;
    BOOST_CHECK_GE(pages_count, size_t(2));
    ms->settings()->max_store_period.setValue(1);
  }
  if (dariadb::utils::fs::path_exists(storage_path)) {
    dariadb::utils::fs::rm(storage_path);
  }
}

BOOST_AUTO_TEST_CASE(Engine_MemOnlyStorage_common_test) {
  const size_t chunk_size = 128;

  const dariadb::Time from = 0;
  const dariadb::Time to = from + 1000;
  const dariadb::Time step = 10;

  using namespace dariadb;
  using namespace dariadb::storage;

  {
    std::cout << "Engine_MemOnlyStorage_common_test\n";

    auto settings = dariadb::storage::Settings::create();
    settings->chunk_size.setValue(chunk_size);
    std::unique_ptr<Engine> ms{new Engine(settings)};

    dariadb_test::storage_test_check(ms.get(), from, to, step, true, false, false);

    auto pages_count = ms->description().pages_count;
    BOOST_CHECK_EQUAL(pages_count, size_t(0));
    ms->settings()->max_store_period.setValue(1);
    while (true) {
      dariadb::QueryInterval qi({dariadb::Id(0)}, dariadb::Flag(), from, to);
      auto values = ms->readInterval(qi);
      if (values.empty()) {
        break;
      } else {
        std::cout << "values !empty() " << values.size() << std::endl;
        dariadb::utils::sleep_mls(500);
      }
    }
  }
}

BOOST_AUTO_TEST_CASE(Engine_Cache_common_test) {
  const std::string storage_path = "testStorage";

  const size_t chunk_size = 128;
  const dariadb::Time from = 0;
  const dariadb::Time to = from + 1000;
  const dariadb::Time step = 10;

  using namespace dariadb;
  using namespace dariadb::storage;

  {
    std::cout << "Engine_Cache_common_test\n";
    if (dariadb::utils::fs::path_exists(storage_path)) {
      dariadb::utils::fs::rm(storage_path);
    }

    auto settings = dariadb::storage::Settings::create(storage_path);
    settings->strategy.setValue(STRATEGY::CACHE);
    settings->chunk_size.setValue(chunk_size);
    settings->memory_limit.setValue(50 * 1024);
    settings->wal_file_size.setValue(2000);
    std::unique_ptr<Engine> ms{new Engine(settings)};

    dariadb_test::storage_test_check(ms.get(), from, to, step, true, true, false);

    auto descr = ms->description();
    BOOST_CHECK_GT(descr.pages_count, size_t(0));
    ms->settings()->max_store_period.setValue(1);
  }
  if (dariadb::utils::fs::path_exists(storage_path)) {
    dariadb::utils::fs::rm(storage_path);
  }
}
<|endoftext|>


<commit_before>/////////////////////////////////////////////////////////////////////////
// $Id$
/////////////////////////////////////////////////////////////////////////
//
//  I grabbed these CRC routines from the following source:
//    http://www.landfield.com/faqs/compression-faq/part1/section-25.html
//
//  These routines are very useful, so I'm including them in bochs.
//  They are not covered by the license, as they are not my doing.
//  My gratitude to the author for offering them on the 'net.
//
//  I only changed the u_long to Bit32u, and u_char to Bit8u, and gave
//  the functions prototypes.
//
//  -Kevin
//
//  **************************************************************************
//  The following C code (by Rob Warnock <rpw3@sgi.com>) does CRC-32 in
//  BigEndian/BigEndian byte/bit order.  That is, the data is sent most
//  significant byte first, and each of the bits within a byte is sent most
//  significant bit first, as in FDDI. You will need to twiddle with it to do
//  Ethernet CRC, i.e., BigEndian/LittleEndian byte/bit order. [Left as an
//  exercise for the reader.]
//
//  The CRCs this code generates agree with the vendor-supplied Verilog models
//  of several of the popular FDDI "MAC" chips.
//  **************************************************************************

#include "config.h"

/* Initialized first time "crc32()" is called. If you prefer, you can
 * statically initialize it at compile time. [Another exercise.]
 */
static Bit32u crc32_table[256];

/*
 * Build auxiliary table for parallel byte-at-a-time CRC-32.
 */
#define CRC32_POLY 0x04c11db7     /* AUTODIN II, Ethernet, & FDDI */

static void init_crc32(void)
{
        int i, j;
        Bit32u c;

        for (i = 0; i < 256; ++i) {
                for (c = i << 24, j = 8; j > 0; --j)
                        c = c & 0x80000000 ? (c << 1) ^ CRC32_POLY : (c << 1);
                crc32_table[i] = c;
        }
}

Bit32u crc32(const Bit8u *buf, int len)
{
        const Bit8u *p;
        Bit32u crc;

        if (!crc32_table[1])    /* if not already done, */
                init_crc32();   /* build table */

        crc = 0xffffffff;       /* preload shift register, per CRC-32 spec */
        for (p = buf; len > 0; ++p, --len)
                crc = (crc << 8) ^ crc32_table[(crc >> 24) ^ *p];
        return ~crc;            /* transmit complement, per CRC-32 spec */
}
<commit_msg>Indent changes<commit_after>/////////////////////////////////////////////////////////////////////////
// $Id$
/////////////////////////////////////////////////////////////////////////
//
//  I grabbed these CRC routines from the following source:
//    http://www.landfield.com/faqs/compression-faq/part1/section-25.html
//
//  These routines are very useful, so I'm including them in bochs.
//  They are not covered by the license, as they are not my doing.
//  My gratitude to the author for offering them on the 'net.
//
//  I only changed the u_long to Bit32u, and u_char to Bit8u, and gave
//  the functions prototypes.
//
//  -Kevin
//
//  **************************************************************************
//  The following C code (by Rob Warnock <rpw3@sgi.com>) does CRC-32 in
//  BigEndian/BigEndian byte/bit order.  That is, the data is sent most
//  significant byte first, and each of the bits within a byte is sent most
//  significant bit first, as in FDDI. You will need to twiddle with it to do
//  Ethernet CRC, i.e., BigEndian/LittleEndian byte/bit order. [Left as an
//  exercise for the reader.]
//
//  The CRCs this code generates agree with the vendor-supplied Verilog models
//  of several of the popular FDDI "MAC" chips.
//  **************************************************************************

#include "config.h"

/* Initialized first time "crc32()" is called. If you prefer, you can
 * statically initialize it at compile time. [Another exercise.]
 */
static Bit32u crc32_table[256];

/*
 * Build auxiliary table for parallel byte-at-a-time CRC-32.
 */
#define CRC32_POLY 0x04c11db7     /* AUTODIN II, Ethernet, & FDDI */

static void init_crc32(void)
{
  int i, j;
  Bit32u c;

  for (i = 0; i < 256; ++i) {
    for (c = i << 24, j = 8; j > 0; --j)
      c = c & 0x80000000 ? (c << 1) ^ CRC32_POLY : (c << 1);
    crc32_table[i] = c;
  }
}

Bit32u crc32(const Bit8u *buf, int len)
{
  const Bit8u *p;
  Bit32u crc;

  if (!crc32_table[1])    /* if not already done, */
    init_crc32();   /* build table */

  crc = 0xffffffff;       /* preload shift register, per CRC-32 spec */
  for (p = buf; len > 0; ++p, --len)
    crc = (crc << 8) ^ crc32_table[(crc >> 24) ^ *p];
  return ~crc;            /* transmit complement, per CRC-32 spec */
}
<|endoftext|>


<commit_before>#include "ItemUtils.h"
#include <Items/ItemComponent.h>
#include <Items/ItemContainerComponent.h>
#include <Scene/SceneComponent.h>
#include <Physics/RigidBodyComponent.h>

namespace DEM::RPG
{

Game::HEntity AddItemsIntoContainer(Game::CGameWorld& World, Game::HEntity Container, Game::HEntity ItemStackEntity, bool Merge)
{
	auto pItemStack = World.FindComponent<CItemStackComponent>(ItemStackEntity);
	if (!pItemStack) return {};

	auto pItem = FindItemComponent<const CItemComponent>(World, ItemStackEntity, *pItemStack);
	if (!pItem) return {};

	auto pContainer = World.FindComponent<CItemContainerComponent>(Container);
	if (!pContainer) return {};

	// Fail if this item can't be placed into the container
	// TODO: split stack, fill available container space!
	// bool flag in args to enable this? return actually added count / remaining stack ID?
	CContainerStats Stats;
	CalcContainerStats(World, *pContainer, Stats);
	if (Stats.FreeWeight < pItemStack->Count * pItem->Weight) return {};
	if (Stats.FreeVolume < pItemStack->Count * pItem->Volume) return {};

	// Try to merge new items into existing stack
	if (Merge && !pItemStack->Modified)
	{
		for (auto MergeAcceptorID : pContainer->Items)
		{
			auto pMergeTo = World.FindComponent<CItemStackComponent>(MergeAcceptorID);
			if (pMergeTo && pMergeTo->Prototype == pItemStack->Prototype && !pMergeTo->Modified)
			{
				pMergeTo->Count += pItemStack->Count;
				World.DeleteEntity(ItemStackEntity);
				return MergeAcceptorID;
			}
		}
	}

	// If not merged, transfer a stack into the container

	World.RemoveComponent<Game::CSceneComponent>(ItemStackEntity);
	World.RemoveComponent<Game::CRigidBodyComponent>(ItemStackEntity);

	// TODO: allow inserting into specified index!
	pContainer->Items.push_back(ItemStackEntity);

	return ItemStackEntity;
}
//---------------------------------------------------------------------

//!!!check dropping near another item stack or pile (temporary container)! bool flag 'allow merging'?
bool DropItemsToLocation(Game::CGameWorld& World, Game::HEntity ItemStackEntity, const Math::CTransformSRT& Tfm)
{
	auto pItemStack = World.FindComponent<CItemStackComponent>(ItemStackEntity);
	if (!pItemStack) return false;

	const CItemComponent* pItem = FindItemComponent<const CItemComponent>(World, ItemStackEntity, *pItemStack);
	if (!pItem) return false;

	// TODO:
	// If merging enabled, query item stacks and tmp item containers in accessible range, not owned by someone else
	// If tmp item containers are found, add the stack to the closest one (take lookat dir into account?)
	// Else if stacks are found, try to merge into the closest one (take lookat dir into account?)
	// If not merged, create tmp item container and add found stack and our stack to it
	// If nothing found, drop a new item object into location (create scene and RB)

	if (pItem->WorldModelID)
	{
		auto pSceneComponent = World.AddComponent<Game::CSceneComponent>(ItemStackEntity);
		pSceneComponent->RootNode->RemoveFromParent();
		pSceneComponent->AssetID = pItem->WorldModelID;
		pSceneComponent->SetLocalTransform(Tfm);
	}

	if (pItem->WorldPhysicsID)
	{
		auto pPhysicsComponent = World.AddComponent<Game::CRigidBodyComponent>(ItemStackEntity);
		pPhysicsComponent->ShapeAssetID = pItem->WorldPhysicsID;
		pPhysicsComponent->Mass = pItemStack->Count * pItem->Weight;
		pPhysicsComponent->CollisionGroupID = CStrID("PhysicalDynamic|Interactable");
		// TODO: physics material?
	}

	return true;
}
//---------------------------------------------------------------------

// TODO: use container as an std::optional hint? or handle this in user API and call RemoveItemsFromContainer only when needed?
void RemoveItemsFromContainer(Game::CGameWorld& World, Game::HEntity ItemStackEntity, Game::HEntity Container)
{
	auto pContainer = World.FindComponent<CItemContainerComponent>(Container);
	if (!pContainer) return;

	pContainer->Items.erase(std::remove(pContainer->Items.begin(), pContainer->Items.end(), ItemStackEntity));
}
//---------------------------------------------------------------------

void CalcContainerStats(Game::CGameWorld& World, const CItemContainerComponent& Container, CContainerStats& OutStats)
{
	OutStats.UsedWeight = 0.f;
	OutStats.UsedVolume = 0.f;
	OutStats.Price = 0;
	for (auto ItemEntityID : Container.Items)
	{
		auto pStack = World.FindComponent<const CItemStackComponent>(ItemEntityID);
		if (!pStack) continue;

		if (auto pItem = FindItemComponent<const CItemComponent>(World, ItemEntityID, *pStack))
		{
			OutStats.UsedWeight += pStack->Count * pItem->Weight;
			OutStats.UsedVolume += pStack->Count * pItem->Volume;
			OutStats.Price += pStack->Count * pItem->Price; //???what to count? only valuable or money-like items?
		}
	}

	OutStats.FreeWeight = (Container.MaxWeight <= 0.f) ? FLT_MAX : (Container.MaxWeight - OutStats.UsedWeight);
	OutStats.FreeVolume = (Container.MaxVolume <= 0.f) ? FLT_MAX : (Container.MaxVolume - OutStats.UsedVolume);
}
//---------------------------------------------------------------------

}
<commit_msg>Protect from re-adding the same item stack to the container<commit_after>#include "ItemUtils.h"
#include <Items/ItemComponent.h>
#include <Items/ItemContainerComponent.h>
#include <Scene/SceneComponent.h>
#include <Physics/RigidBodyComponent.h>

namespace DEM::RPG
{

Game::HEntity AddItemsIntoContainer(Game::CGameWorld& World, Game::HEntity Container, Game::HEntity StackID, bool Merge)
{
	auto pItemStack = World.FindComponent<CItemStackComponent>(StackID);
	if (!pItemStack) return {};

	auto pItem = FindItemComponent<const CItemComponent>(World, StackID, *pItemStack);
	if (!pItem) return {};

	auto pContainer = World.FindComponent<CItemContainerComponent>(Container);
	if (!pContainer) return {};

	// Check that we don't insert already contained stack, and find a merge stack if posible
	if (std::find(pContainer->Items.cbegin(), pContainer->Items.cend(), StackID) != pContainer->Items.cend())
		return {};

	// Fail if this item can't be placed into the container
	// TODO: split stack, fill available container space!
	// bool flag in args to enable this? return actually added count / remaining stack ID?
	CContainerStats Stats;
	CalcContainerStats(World, *pContainer, Stats);
	if (Stats.FreeWeight < pItemStack->Count * pItem->Weight) return {};
	if (Stats.FreeVolume < pItemStack->Count * pItem->Volume) return {};

	// Try to merge new items into existing stack
	if (Merge && !pItemStack->Modified)
	{
		for (auto MergeAcceptorID : pContainer->Items)
		{
			auto pMergeTo = World.FindComponent<CItemStackComponent>(MergeAcceptorID);
			if (pMergeTo && pMergeTo->Prototype == pItemStack->Prototype && !pMergeTo->Modified)
			{
				pMergeTo->Count += pItemStack->Count;
				World.DeleteEntity(StackID);
				return MergeAcceptorID;
			}
		}
	}

	// If not merged, transfer a stack into the container

	World.RemoveComponent<Game::CSceneComponent>(StackID);
	World.RemoveComponent<Game::CRigidBodyComponent>(StackID);

	// TODO: allow inserting into specified index!
	pContainer->Items.push_back(StackID);

	return StackID;
}
//---------------------------------------------------------------------

//!!!check dropping near another item stack or pile (temporary container)! bool flag 'allow merging'?
bool DropItemsToLocation(Game::CGameWorld& World, Game::HEntity ItemStackEntity, const Math::CTransformSRT& Tfm)
{
	auto pItemStack = World.FindComponent<CItemStackComponent>(ItemStackEntity);
	if (!pItemStack) return false;

	const CItemComponent* pItem = FindItemComponent<const CItemComponent>(World, ItemStackEntity, *pItemStack);
	if (!pItem) return false;

	// TODO:
	// If merging enabled, query item stacks and tmp item containers in accessible range, not owned by someone else
	// If tmp item containers are found, add the stack to the closest one (take lookat dir into account?)
	// Else if stacks are found, try to merge into the closest one (take lookat dir into account?)
	// If not merged, create tmp item container and add found stack and our stack to it
	// If nothing found, drop a new item object into location (create scene and RB)

	if (pItem->WorldModelID)
	{
		auto pSceneComponent = World.AddComponent<Game::CSceneComponent>(ItemStackEntity);
		pSceneComponent->RootNode->RemoveFromParent();
		pSceneComponent->AssetID = pItem->WorldModelID;
		pSceneComponent->SetLocalTransform(Tfm);
	}

	if (pItem->WorldPhysicsID)
	{
		auto pPhysicsComponent = World.AddComponent<Game::CRigidBodyComponent>(ItemStackEntity);
		pPhysicsComponent->ShapeAssetID = pItem->WorldPhysicsID;
		pPhysicsComponent->Mass = pItemStack->Count * pItem->Weight;
		pPhysicsComponent->CollisionGroupID = CStrID("PhysicalDynamic|Interactable");
		// TODO: physics material?
	}

	return true;
}
//---------------------------------------------------------------------

// TODO: use container as an std::optional hint? or handle this in user API and call RemoveItemsFromContainer only when needed?
void RemoveItemsFromContainer(Game::CGameWorld& World, Game::HEntity ItemStackEntity, Game::HEntity Container)
{
	auto pContainer = World.FindComponent<CItemContainerComponent>(Container);
	if (!pContainer) return;

	pContainer->Items.erase(std::remove(pContainer->Items.begin(), pContainer->Items.end(), ItemStackEntity));
}
//---------------------------------------------------------------------

void CalcContainerStats(Game::CGameWorld& World, const CItemContainerComponent& Container, CContainerStats& OutStats)
{
	OutStats.UsedWeight = 0.f;
	OutStats.UsedVolume = 0.f;
	OutStats.Price = 0;
	for (auto ItemEntityID : Container.Items)
	{
		auto pStack = World.FindComponent<const CItemStackComponent>(ItemEntityID);
		if (!pStack) continue;

		if (auto pItem = FindItemComponent<const CItemComponent>(World, ItemEntityID, *pStack))
		{
			OutStats.UsedWeight += pStack->Count * pItem->Weight;
			OutStats.UsedVolume += pStack->Count * pItem->Volume;
			OutStats.Price += pStack->Count * pItem->Price; //???what to count? only valuable or money-like items?
		}
	}

	OutStats.FreeWeight = (Container.MaxWeight <= 0.f) ? FLT_MAX : (Container.MaxWeight - OutStats.UsedWeight);
	OutStats.FreeVolume = (Container.MaxVolume <= 0.f) ? FLT_MAX : (Container.MaxVolume - OutStats.UsedVolume);
}
//---------------------------------------------------------------------

}
<|endoftext|>


<commit_before><commit_msg>Libport.Hash: fixes.<commit_after><|endoftext|>


<commit_before>#include "views/SystemView.h"
#include "SystemData.h"
#include "Renderer.h"
#include "Log.h"
#include "Window.h"
#include "views/ViewController.h"
#include "animations/LambdaAnimation.h"
#include "SystemData.h"
#include "Settings.h"
#include "Util.h"

#define SELECTED_SCALE 1.5f
#define LOGO_PADDING ((logoSize().x() * (SELECTED_SCALE - 1)/2) + (mSize.x() * 0.06f))
#define BAND_HEIGHT (logoSize().y() * SELECTED_SCALE)

SystemView::SystemView(Window* window) : IList<SystemViewData, SystemData*>(window, LIST_SCROLL_STYLE_SLOW, LIST_ALWAYS_LOOP),
	mSystemInfo(window, "SYSTEM INFO", Font::get(FONT_SIZE_SMALL), 0x33333300, ALIGN_CENTER)
{
	mCamOffset = 0;
	mExtrasCamOffset = 0;
	mExtrasFadeOpacity = 0.0f;

	setSize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());

	mSystemInfo.setSize(mSize.x(), mSystemInfo.getSize().y() * 1.333f);
	mSystemInfo.setPosition(0, (mSize.y() + BAND_HEIGHT) / 2);

	populate();
}

void SystemView::populate()
{
	mEntries.clear();

	for(auto it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); it++)
	{
		const std::shared_ptr<ThemeData>& theme = (*it)->getTheme();

		Entry e;
		e.name = (*it)->getName();
		e.object = *it;

		// make logo
		if(theme->getElement("system", "logo", "image"))
		{
			ImageComponent* logo = new ImageComponent(mWindow);
			logo->setMaxSize(Eigen::Vector2f(logoSize().x(), logoSize().y()));
			logo->applyTheme((*it)->getTheme(), "system", "logo", ThemeFlags::PATH);
			logo->setPosition((logoSize().x() - logo->getSize().x()) / 2, (logoSize().y() - logo->getSize().y()) / 2); // center
			e.data.logo = std::shared_ptr<GuiComponent>(logo);

			ImageComponent* logoSelected = new ImageComponent(mWindow);
			logoSelected->setMaxSize(Eigen::Vector2f(logoSize().x() * SELECTED_SCALE, logoSize().y() * SELECTED_SCALE * 0.70f));
			logoSelected->applyTheme((*it)->getTheme(), "system", "logo", ThemeFlags::PATH);
			logoSelected->setPosition((logoSize().x() - logoSelected->getSize().x()) / 2,
				(logoSize().y() - logoSelected->getSize().y()) / 2); // center
			e.data.logoSelected = std::shared_ptr<GuiComponent>(logoSelected);
		}else{
			// no logo in theme; use text
			TextComponent* text = new TextComponent(mWindow,
				(*it)->getName(),
				Font::get(FONT_SIZE_LARGE),
				0x000000FF,
				ALIGN_CENTER);
			text->setSize(logoSize());
			e.data.logo = std::shared_ptr<GuiComponent>(text);

			TextComponent* textSelected = new TextComponent(mWindow,
				(*it)->getName(),
				Font::get((int)(FONT_SIZE_LARGE * SELECTED_SCALE)),
				0x000000FF,
				ALIGN_CENTER);
			textSelected->setSize(logoSize());
			e.data.logoSelected = std::shared_ptr<GuiComponent>(textSelected);
		}

		// make background extras
		e.data.backgroundExtras = std::shared_ptr<ThemeExtras>(new ThemeExtras(mWindow));
		e.data.backgroundExtras->setExtras(ThemeData::makeExtras((*it)->getTheme(), "system", mWindow));

		this->add(e);
	}
}

void SystemView::goToSystem(SystemData* system, bool animate)
{
	setCursor(system);

	if(!animate)
		finishAnimation(0);
}

bool SystemView::input(InputConfig* config, Input input)
{
	if(input.value != 0)
	{
		if(config->getDeviceId() == DEVICE_KEYBOARD && input.value && input.id == SDLK_r && SDL_GetModState() & KMOD_LCTRL && Settings::getInstance()->getBool("Debug"))
		{
			LOG(LogInfo) << " Reloading SystemList view";

			// reload themes
			for(auto it = mEntries.begin(); it != mEntries.end(); it++)
				it->object->loadTheme();

			populate();
			updateHelpPrompts();
			return true;
		}
		if(config->isMappedTo("left", input))
		{
			listInput(-1);
			return true;
		}
		if(config->isMappedTo("right", input))
		{
			listInput(1);
			return true;
		}
		if(config->isMappedTo("a", input))
		{
			stopScrolling();
			ViewController::get()->goToGameList(getSelected());
			return true;
		}
	}else{
		if(config->isMappedTo("left", input) || config->isMappedTo("right", input))
			listInput(0);
	}

	return GuiComponent::input(config, input);
}

void SystemView::update(int deltaTime)
{
	listUpdate(deltaTime);
	GuiComponent::update(deltaTime);
}

void SystemView::onCursorChanged(const CursorState& state)
{
	// update help style
	updateHelpPrompts();

	float startPos = mCamOffset;

	float posMax = (float)mEntries.size();
	float target = (float)mCursor;

	// what's the shortest way to get to our target?
	// it's one of these...

	float endPos = target; // directly
	float dist = abs(endPos - startPos);

	if(abs(target + posMax - startPos) < dist)
		endPos = target + posMax; // loop around the end (0 -> max)
	if(abs(target - posMax - startPos) < dist)
		endPos = target - posMax; // loop around the start (max - 1 -> -1)

	// animate mSystemInfo's opacity (fade out, wait, fade back in)

	cancelAnimation(1);
	cancelAnimation(2);

	const float infoStartOpacity = mSystemInfo.getOpacity() / 255.f;

	Animation* infoFadeOut = new LambdaAnimation(
		[infoStartOpacity, this] (float t)
	{
		mSystemInfo.setOpacity((unsigned char)(lerp<float>(infoStartOpacity, 0.f, t) * 255));
	}, (int)(infoStartOpacity * 150));

	unsigned int gameCount = getSelected()->getGameCount();

	// also change the text after we've fully faded out
	setAnimation(infoFadeOut, 0, [this, gameCount] {
		std::stringstream ss;

		// only display a game count if there are at least 2 games
		if(gameCount > 1)
			ss << gameCount << " GAMES AVAILABLE";

		mSystemInfo.setText(ss.str());
	}, false, 1);

	// only display a game count if there are at least 2 games
	if(gameCount > 1)
	{
		Animation* infoFadeIn = new LambdaAnimation(
			[this](float t)
		{
			mSystemInfo.setOpacity((unsigned char)(lerp<float>(0.f, 1.f, t) * 255));
		}, 300);

		// wait 600ms to fade in
		setAnimation(infoFadeIn, 2000, nullptr, false, 2);
	}

	// no need to animate transition, we're not going anywhere (probably mEntries.size() == 1)
	if(endPos == mCamOffset && endPos == mExtrasCamOffset)
		return;

	Animation* anim;
	if(Settings::getInstance()->getString("TransitionStyle") == "fade")
	{
		float startExtrasFade = mExtrasFadeOpacity;
		anim = new LambdaAnimation(
			[startExtrasFade, startPos, endPos, posMax, this](float t)
		{
			t -= 1;
			float f = lerp<float>(startPos, endPos, t*t*t + 1);
			if(f < 0)
				f += posMax;
			if(f >= posMax)
				f -= posMax;

			this->mCamOffset = f;

			t += 1;
			if(t < 0.3f)
				this->mExtrasFadeOpacity = lerp<float>(0.0f, 1.0f, t / 0.3f + startExtrasFade);
			else if(t < 0.7f)
				this->mExtrasFadeOpacity = 1.0f;
			else
				this->mExtrasFadeOpacity = lerp<float>(1.0f, 0.0f, (t - 0.7f) / 0.3f);

			if(t > 0.5f)
				this->mExtrasCamOffset = endPos;

		}, 500);
	}
	else{ // slide
		anim = new LambdaAnimation(
			[startPos, endPos, posMax, this](float t)
		{
			t -= 1;
			float f = lerp<float>(startPos, endPos, t*t*t + 1);
			if(f < 0)
				f += posMax;
			if(f >= posMax)
				f -= posMax;

			this->mCamOffset = f;
			this->mExtrasCamOffset = f;
		}, 500);
	}

	setAnimation(anim, 0, nullptr, false, 0);
}

void SystemView::render(const Eigen::Affine3f& parentTrans)
{
	if(size() == 0)
		return;

	Eigen::Affine3f trans = getTransform() * parentTrans;

	// draw the list elements (titles, backgrounds, logos)
	const float logoSizeX = logoSize().x() + LOGO_PADDING;

	int logoCount = (int)(mSize.x() / logoSizeX) + 2; // how many logos we need to draw
	int center = (int)(mCamOffset);

	if(mEntries.size() == 1)
		logoCount = 1;

	// draw background extras
	Eigen::Affine3f extrasTrans = trans;
	int extrasCenter = (int)mExtrasCamOffset;
	for(int i = extrasCenter - 1; i < extrasCenter + 2; i++)
	{
		int index = i;
		while(index < 0)
			index += mEntries.size();
		while(index >= (int)mEntries.size())
			index -= mEntries.size();

		extrasTrans.translation() = trans.translation() + Eigen::Vector3f((i - mExtrasCamOffset) * mSize.x(), 0, 0);

		Eigen::Vector2i clipRect = Eigen::Vector2i((int)((i - mExtrasCamOffset) * mSize.x()), 0);
		Renderer::pushClipRect(clipRect, mSize.cast<int>());
		mEntries.at(index).data.backgroundExtras->render(extrasTrans);
		Renderer::popClipRect();
	}

	// fade extras if necessary
	if(mExtrasFadeOpacity)
	{
		Renderer::setMatrix(trans);
		Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), 0x00000000 | (unsigned char)(mExtrasFadeOpacity * 255));
	}

	// draw logos
	float xOff = (mSize.x() - logoSize().x())/2 - (mCamOffset * logoSizeX);
	float yOff = (mSize.y() - logoSize().y())/2;

	// background behind the logos
	Renderer::setMatrix(trans);
	Renderer::drawRect(0.f, (mSize.y() - BAND_HEIGHT) / 2, mSize.x(), BAND_HEIGHT, 0xFFFFFFD8);

	Eigen::Affine3f logoTrans = trans;
	for(int i = center - logoCount/2; i < center + logoCount/2 + 1; i++)
	{
		int index = i;
		while(index < 0)
			index += mEntries.size();
		while(index >= (int)mEntries.size())
			index -= mEntries.size();

		logoTrans.translation() = trans.translation() + Eigen::Vector3f(i * logoSizeX + xOff, yOff, 0);

		if(index == mCursor) //scale our selection up
		{
			// selected
			const std::shared_ptr<GuiComponent>& comp = mEntries.at(index).data.logoSelected;
			comp->setOpacity(0xFF);
			comp->render(logoTrans);
		}else{
			// not selected
			const std::shared_ptr<GuiComponent>& comp = mEntries.at(index).data.logo;
			comp->setOpacity(0x80);
			comp->render(logoTrans);
		}
	}

	Renderer::setMatrix(trans);
	Renderer::drawRect(mSystemInfo.getPosition().x(), mSystemInfo.getPosition().y() - 1, mSize.x(), mSystemInfo.getSize().y(), 0xDDDDDD00 | (unsigned char)(mSystemInfo.getOpacity() / 255.f * 0xD8));
	mSystemInfo.render(trans);
}

std::vector<HelpPrompt> SystemView::getHelpPrompts()
{
	std::vector<HelpPrompt> prompts;
	prompts.push_back(HelpPrompt("left/right", "choose"));
	prompts.push_back(HelpPrompt("a", "select"));
	return prompts;
}

HelpStyle SystemView::getHelpStyle()
{
	HelpStyle style;
	style.applyTheme(mEntries.at(mCursor).object->getTheme(), "system");
	return style;
}
<commit_msg>Update SystemView.cpp<commit_after>#include "views/SystemView.h"
#include "SystemData.h"
#include "Renderer.h"
#include "Log.h"
#include "Window.h"
#include "views/ViewController.h"
#include "animations/LambdaAnimation.h"
#include "SystemData.h"
#include "Settings.h"
#include "Util.h"
#include "ThemeData.h"
#include "Music.h"

#define SELECTED_SCALE 1.5f
#define LOGO_PADDING ((logoSize().x() * (SELECTED_SCALE - 1)/2) + (mSize.x() * 0.06f))
#define BAND_HEIGHT (logoSize().y() * SELECTED_SCALE)

SystemView::SystemView(Window* window) : IList<SystemViewData, SystemData*>(window, LIST_SCROLL_STYLE_SLOW, LIST_ALWAYS_LOOP),
	mSystemInfo(window, "SYSTEM INFO", Font::get(FONT_SIZE_SMALL), 0x33333300, ALIGN_CENTER)
{
	mCamOffset = 0;
	mExtrasCamOffset = 0;
	mExtrasFadeOpacity = 0.0f;

	setSize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());

	mSystemInfo.setSize(mSize.x(), mSystemInfo.getSize().y() * 1.333f);
	mSystemInfo.setPosition(0, (mSize.y() + BAND_HEIGHT) / 2);

	populate();
}

void SystemView::populate()
{
	mEntries.clear();

	for(auto it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); it++)
	{
		const std::shared_ptr<ThemeData>& theme = (*it)->getTheme();

		Entry e;
		e.name = (*it)->getName();
		e.object = *it;

		// make logo
		if(theme->getElement("system", "logo", "image"))
		{
			ImageComponent* logo = new ImageComponent(mWindow);
			logo->setMaxSize(Eigen::Vector2f(logoSize().x(), logoSize().y()));
			logo->applyTheme((*it)->getTheme(), "system", "logo", ThemeFlags::PATH);
			logo->setPosition((logoSize().x() - logo->getSize().x()) / 2, (logoSize().y() - logo->getSize().y()) / 2); // center
			e.data.logo = std::shared_ptr<GuiComponent>(logo);

			ImageComponent* logoSelected = new ImageComponent(mWindow);
			logoSelected->setMaxSize(Eigen::Vector2f(logoSize().x() * SELECTED_SCALE, logoSize().y() * SELECTED_SCALE * 0.70f));
			logoSelected->applyTheme((*it)->getTheme(), "system", "logo", ThemeFlags::PATH);
			logoSelected->setPosition((logoSize().x() - logoSelected->getSize().x()) / 2,
				(logoSize().y() - logoSelected->getSize().y()) / 2); // center
			e.data.logoSelected = std::shared_ptr<GuiComponent>(logoSelected);
		}else{
			// no logo in theme; use text
			TextComponent* text = new TextComponent(mWindow,
				(*it)->getName(),
				Font::get(FONT_SIZE_LARGE),
				0x000000FF,
				ALIGN_CENTER);
			text->setSize(logoSize());
			e.data.logo = std::shared_ptr<GuiComponent>(text);

			TextComponent* textSelected = new TextComponent(mWindow,
				(*it)->getName(),
				Font::get((int)(FONT_SIZE_LARGE * SELECTED_SCALE)),
				0x000000FF,
				ALIGN_CENTER);
			textSelected->setSize(logoSize());
			e.data.logoSelected = std::shared_ptr<GuiComponent>(textSelected);
		}

		// make background extras
		e.data.backgroundExtras = std::shared_ptr<ThemeExtras>(new ThemeExtras(mWindow));
		e.data.backgroundExtras->setExtras(ThemeData::makeExtras((*it)->getTheme(), "system", mWindow));

		this->add(e);
	}
}

void SystemView::goToSystem(SystemData* system, bool animate)
{
	setCursor(system);

	if(!animate)
		finishAnimation(0);
}

bool SystemView::input(InputConfig* config, Input input)
{
	if(input.value != 0)
	{
		if(config->getDeviceId() == DEVICE_KEYBOARD && input.value && input.id == SDLK_r && SDL_GetModState() & KMOD_LCTRL && Settings::getInstance()->getBool("Debug"))
		{
			LOG(LogInfo) << " Reloading SystemList view";

			// reload themes
			for(auto it = mEntries.begin(); it != mEntries.end(); it++)
				it->object->loadTheme();

			populate();
			updateHelpPrompts();
			return true;
		}
		if(config->isMappedTo("left", input))
		{
			listInput(-1);
			return true;
		}
		if(config->isMappedTo("right", input))
		{
			listInput(1);
			return true;
		}
		if(config->isMappedTo("a", input))
		{
			stopScrolling();
			ViewController::get()->goToGameList(getSelected());
			return true;
		}
	}else{
		if(config->isMappedTo("left", input) || config->isMappedTo("right", input))
			listInput(0);
	}

	return GuiComponent::input(config, input);
}

void SystemView::update(int deltaTime)
{
	listUpdate(deltaTime);
	GuiComponent::update(deltaTime);
}

void SystemView::onCursorChanged(const CursorState& state)
{

	if(lastSystem != getSelected()){
		lastSystem = getSelected();
		Music::startMusic(getSelected()->getTheme());
	}
	// update help style
	updateHelpPrompts();

	float startPos = mCamOffset;

	float posMax = (float)mEntries.size();
	float target = (float)mCursor;

	// what's the shortest way to get to our target?
	// it's one of these...

	float endPos = target; // directly
	float dist = abs(endPos - startPos);

	if(abs(target + posMax - startPos) < dist)
		endPos = target + posMax; // loop around the end (0 -> max)
	if(abs(target - posMax - startPos) < dist)
		endPos = target - posMax; // loop around the start (max - 1 -> -1)

	// animate mSystemInfo's opacity (fade out, wait, fade back in)

	cancelAnimation(1);
	cancelAnimation(2);

	const float infoStartOpacity = mSystemInfo.getOpacity() / 255.f;

	Animation* infoFadeOut = new LambdaAnimation(
		[infoStartOpacity, this] (float t)
	{
		mSystemInfo.setOpacity((unsigned char)(lerp<float>(infoStartOpacity, 0.f, t) * 255));
	}, (int)(infoStartOpacity * 150));

	unsigned int gameCount = getSelected()->getGameCount();

	// also change the text after we've fully faded out
	setAnimation(infoFadeOut, 0, [this, gameCount] {
		std::stringstream ss;

		// only display a game count if there are at least 2 games
		if(gameCount > 1)
			ss << gameCount << " GAMES AVAILABLE";

		mSystemInfo.setText(ss.str());
	}, false, 1);

	// only display a game count if there are at least 2 games
	if(gameCount > 1)
	{
		Animation* infoFadeIn = new LambdaAnimation(
			[this](float t)
		{
			mSystemInfo.setOpacity((unsigned char)(lerp<float>(0.f, 1.f, t) * 255));
		}, 300);

		// wait 600ms to fade in
		setAnimation(infoFadeIn, 2000, nullptr, false, 2);
	}

	// no need to animate transition, we're not going anywhere (probably mEntries.size() == 1)
	if(endPos == mCamOffset && endPos == mExtrasCamOffset)
		return;

	Animation* anim;
	if(Settings::getInstance()->getString("TransitionStyle") == "fade")
	{
		float startExtrasFade = mExtrasFadeOpacity;
		anim = new LambdaAnimation(
			[startExtrasFade, startPos, endPos, posMax, this](float t)
		{
			t -= 1;
			float f = lerp<float>(startPos, endPos, t*t*t + 1);
			if(f < 0)
				f += posMax;
			if(f >= posMax)
				f -= posMax;

			this->mCamOffset = f;

			t += 1;
			if(t < 0.3f)
				this->mExtrasFadeOpacity = lerp<float>(0.0f, 1.0f, t / 0.3f + startExtrasFade);
			else if(t < 0.7f)
				this->mExtrasFadeOpacity = 1.0f;
			else
				this->mExtrasFadeOpacity = lerp<float>(1.0f, 0.0f, (t - 0.7f) / 0.3f);

			if(t > 0.5f)
				this->mExtrasCamOffset = endPos;

		}, 500);
	}
	else{ // slide
		anim = new LambdaAnimation(
			[startPos, endPos, posMax, this](float t)
		{
			t -= 1;
			float f = lerp<float>(startPos, endPos, t*t*t + 1);
			if(f < 0)
				f += posMax;
			if(f >= posMax)
				f -= posMax;

			this->mCamOffset = f;
			this->mExtrasCamOffset = f;
		}, 500);
	}

	setAnimation(anim, 0, nullptr, false, 0);
}

void SystemView::render(const Eigen::Affine3f& parentTrans)
{
	if(size() == 0)
		return;

	Eigen::Affine3f trans = getTransform() * parentTrans;

	// draw the list elements (titles, backgrounds, logos)
	const float logoSizeX = logoSize().x() + LOGO_PADDING;

	int logoCount = (int)(mSize.x() / logoSizeX) + 2; // how many logos we need to draw
	int center = (int)(mCamOffset);

	if(mEntries.size() == 1)
		logoCount = 1;

	// draw background extras
	Eigen::Affine3f extrasTrans = trans;
	int extrasCenter = (int)mExtrasCamOffset;
	for(int i = extrasCenter - 1; i < extrasCenter + 2; i++)
	{
		int index = i;
		while(index < 0)
			index += mEntries.size();
		while(index >= (int)mEntries.size())
			index -= mEntries.size();

		extrasTrans.translation() = trans.translation() + Eigen::Vector3f((i - mExtrasCamOffset) * mSize.x(), 0, 0);

		Eigen::Vector2i clipRect = Eigen::Vector2i((int)((i - mExtrasCamOffset) * mSize.x()), 0);
		Renderer::pushClipRect(clipRect, mSize.cast<int>());
		mEntries.at(index).data.backgroundExtras->render(extrasTrans);
		Renderer::popClipRect();
	}

	// fade extras if necessary
	if(mExtrasFadeOpacity)
	{
		Renderer::setMatrix(trans);
		Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), 0x00000000 | (unsigned char)(mExtrasFadeOpacity * 255));
	}

	// draw logos
	float xOff = (mSize.x() - logoSize().x())/2 - (mCamOffset * logoSizeX);
	float yOff = (mSize.y() - logoSize().y())/2;

	// background behind the logos
	Renderer::setMatrix(trans);
	Renderer::drawRect(0.f, (mSize.y() - BAND_HEIGHT) / 2, mSize.x(), BAND_HEIGHT, 0xFFFFFFD8);

	Eigen::Affine3f logoTrans = trans;
	for(int i = center - logoCount/2; i < center + logoCount/2 + 1; i++)
	{
		int index = i;
		while(index < 0)
			index += mEntries.size();
		while(index >= (int)mEntries.size())
			index -= mEntries.size();

		logoTrans.translation() = trans.translation() + Eigen::Vector3f(i * logoSizeX + xOff, yOff, 0);

		if(index == mCursor) //scale our selection up
		{
			// selected
			const std::shared_ptr<GuiComponent>& comp = mEntries.at(index).data.logoSelected;
			comp->setOpacity(0xFF);
			comp->render(logoTrans);
		}else{
			// not selected
			const std::shared_ptr<GuiComponent>& comp = mEntries.at(index).data.logo;
			comp->setOpacity(0x80);
			comp->render(logoTrans);
		}
	}

	Renderer::setMatrix(trans);
	Renderer::drawRect(mSystemInfo.getPosition().x(), mSystemInfo.getPosition().y() - 1, mSize.x(), mSystemInfo.getSize().y(), 0xDDDDDD00 | (unsigned char)(mSystemInfo.getOpacity() / 255.f * 0xD8));
	mSystemInfo.render(trans);
}

std::vector<HelpPrompt> SystemView::getHelpPrompts()
{
	std::vector<HelpPrompt> prompts;
	prompts.push_back(HelpPrompt("left/right", "choose"));
	prompts.push_back(HelpPrompt("a", "select"));
	return prompts;
}

HelpStyle SystemView::getHelpStyle()
{
	HelpStyle style;
	style.applyTheme(mEntries.at(mCursor).object->getTheme(), "system");
	return style;
}
<|endoftext|>