1
0
mirror of https://github.com/wqking/eventpp.git synced 2024-12-25 23:30:49 +08:00

AnyData stores large data on heap instead of compiling error

This commit is contained in:
wqking 2023-05-05 08:28:00 +08:00
parent b25e2703a5
commit 7afab328dd
4 changed files with 264 additions and 116 deletions

View File

@ -2,19 +2,22 @@
<!--begintoc-->
## Table Of Contents
* [Description](#a2_1)
* [Use AnyData](#a2_2)
* [Header](#a3_1)
* [Class AnyData template parameters](#a3_2)
* [Use AnyData in EventQueue, the simplest but not recommend way](#a3_3)
* [Use AnyData in EventQueue, the recommend way](#a3_4)
* [Extend with new events](#a3_5)
* [Global function](#a2_3)
* [maxSizeOf](#a3_6)
* [Tutorial](#a2_4)
- [Class AnyData reference](#class-anydata-reference)
- [Table Of Contents](#table-of-contents)
- [Description](#description)
- [Use AnyData](#use-anydata)
- [Header](#header)
- [Class AnyData template parameters](#class-anydata-template-parameters)
- [Use AnyData in EventQueue, the simplest but not recommend way](#use-anydata-in-eventqueue-the-simplest-but-not-recommend-way)
- [get](#get)
- [getAddress](#getaddress)
- [isType](#istype)
- [Use AnyData in EventQueue, the recommend way](#use-anydata-in-eventqueue-the-recommend-way)
- [Global function](#global-function)
- [maxSizeOf](#maxsizeof)
- [Tutorial](#tutorial)
<!--endtoc-->
<a id="a2_1"></a>
## Description
Class `AnyData` is a data structure that can hold any data types without any dynamic heap allocation, and `AnyData` can be passed to `EventQueue` in place of the data it holds. The purpose is to eliminate the heap allocation time which is commonly used as `std::shared_ptr`. `AnyData` can improve the performance by about 30%~50%, comparing to heap allocation with `std::shared_ptr`.
@ -119,15 +122,12 @@ eventQueue.enqueue(EventType::key, KeyEvent(123));
eventQueue.enqueue(EventType::mouse, MouseEvent(100, 200));
```
<a id="a2_2"></a>
## Use AnyData
<a id="a3_1"></a>
### Header
eventpp/utilities/anydata.h
<a id="a3_2"></a>
### Class AnyData template parameters
```c++
@ -135,10 +135,9 @@ template <std::size_t maxSize>
class AnyData;
```
`AnyData` requires one constant template parameter. It's the max size of the underlying types. Any data types can be used to construct `AnyData`, as long as the data type size is not larger than `maxSize`. If it's larger, compile time error is produced.
`AnyData` requires one constant template parameter. It's the max size of the underlying types. Any data types with any data size can be used to construct `AnyData`. If the data size is not larger than `maxSize`, the data is stored inside `AnyData`. If it's larger, the data is stored on the heap with dynamic allocation.
`AnyData` uses at least `maxSize` bytes, even if the underlying data is only 1 byte long. So `AnyData` might use slightly more memory than the shared pointer solution, but also may not, because shared pointer solution has other memory overhead.
<a id="a3_3"></a>
### Use AnyData in EventQueue, the simplest but not recommend way
`AnyData` can be used as the callback arguments in EventQueue.
@ -184,7 +183,6 @@ bool isType() const;
Return true if the underlying data type is `T`, false if not.
This function compares the exactly types, it doesn't check any class hierarchy. For example, if an `AnyData` holds `KeyEvent`, then `isType<KeyEvent>()` will return true, but `isType<Event>()` will return false.
<a id="a3_4"></a>
### Use AnyData in EventQueue, the recommend way
```c++
@ -219,26 +217,8 @@ queue.enqueue(EventType::text, std::string("This is a text"));
queue.process();
```
<a id="a3_5"></a>
### Extend with new events
`AnyData` requires compile time known max data size. What if a user needs to add new events but he can't modify the `AnyData` declaration thus can't modify the max size?
The user can add any new events as long as the data size is not larger then the max size. If any new events has larger data, the data can be put in dynamic allocated memory and hold in `std::shared_ptr`. For example,
```c++
class MyLargeEvent : public Event
{
private:
std::shared_ptr<char> myLargeGigaBytesData;
};
```
Then it works as long as `sizeof(MyLargeEvent) <= eventMaxSize`.
<a id="a2_3"></a>
## Global function
<a id="a3_6"></a>
### maxSizeOf
```c++
@ -251,7 +231,6 @@ Return the maximum size of types Ts... For example,
maxSizeOf<KeyEvent, MouseEvent, int, double>();
```
<a id="a2_4"></a>
## Tutorial
Below is the tutorial code. The complete code can be found in `tests/tutorial/tutorial_anydata.cpp`

View File

@ -18,13 +18,17 @@
#include <type_traits>
#include <cstdint>
#include <cassert>
#include <memory>
namespace eventpp {
namespace anydata_internal_ {
template <typename T>
void funcMoveConstruct(void * object, void * buffer);
void funcDeleteObject(void * object)
{
delete static_cast<T *>(object);
}
template <typename T>
void funcFreeObject(void * object)
@ -32,26 +36,6 @@ void funcFreeObject(void * object)
static_cast<T *>(object)->~T();
}
template <typename T>
auto doFuncCopyConstruct(void * object, void * buffer)
-> typename std::enable_if<std::is_copy_constructible<T>::value>::type
{
new (buffer) T(*(T *)object);
}
template <typename T>
auto doFuncCopyConstruct(void * object, void * buffer)
-> typename std::enable_if<! std::is_copy_constructible<T>::value>::type
{
funcMoveConstruct<T>(object, buffer);
}
template <typename T>
void funcCopyConstruct(void * object, void * buffer)
{
doFuncCopyConstruct<T>(object, buffer);
}
template <typename T>
auto doFuncMoveConstruct(void * object, void * buffer)
-> typename std::enable_if<std::is_move_constructible<T>::value>::type
@ -88,10 +72,16 @@ const AnyDataFunctions * doGetAnyDataFunctions()
return &functions;
}
template <typename T>
struct RemoveCvRef
{
using Type = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
};
template <typename T>
const AnyDataFunctions * getAnyDataFunctions()
{
using U = typename std::remove_reference<typename std::remove_cv<T>::type>::type;
using U = typename RemoveCvRef<T>::Type;
return doGetAnyDataFunctions<U>();
}
@ -113,11 +103,56 @@ struct MaxSizeOf <T>
static constexpr std::size_t value = sizeof(T);
};
class LargeData
{
public:
template <typename T>
explicit LargeData(T && object) : data(), deleter() {
using U = typename RemoveCvRef<T>::Type;
deleter = &funcDeleteObject<U>;
data = new U(std::forward<T>(object));
}
~LargeData() {
if(data != nullptr) {
assert(deleter != nullptr);
deleter(data);
}
}
LargeData(LargeData && other) : data(), deleter() {
std::swap(data, other.data);
std::swap(deleter, other.deleter);
}
LargeData(const LargeData & other) = delete;
LargeData & operator = (const LargeData & other) = delete;
LargeData & operator = (LargeData && other) = delete;
const void * getAddress() const {
return data;
}
template <typename T>
bool isType() const {
using U = typename RemoveCvRef<T>::Type;
return deleter == &funcDeleteObject<U>;
}
private:
void * data;
void (*deleter)(void *);
};
} //namespace anydata_internal_
template <std::size_t maxSize>
template <std::size_t maxSize_>
class AnyData
{
private:
using LargeData = anydata_internal_::LargeData;
static constexpr std::size_t maxSize = maxSize_ < sizeof(LargeData) ? sizeof(LargeData) : maxSize_;
static_assert(maxSize > 0, "AnyData: maxSize must be greater than 0");
public:
@ -128,13 +163,20 @@ public:
}
template <typename T>
AnyData(T && object) : functions(anydata_internal_::getAnyDataFunctions<T>()), buffer() {
AnyData(T && object,
typename std::enable_if<(sizeof(typename anydata_internal_::RemoveCvRef<T>::Type) <= maxSize)>::type * = 0)
: functions(anydata_internal_::getAnyDataFunctions<T>()), buffer() {
using U = typename std::remove_reference<T>::type;
static_assert(sizeof(U) <= maxSize, "AnyData: object size must not be greater than maxSize");
new (buffer.data()) U(std::forward<T>(object));
}
template <typename T>
AnyData(T && object,
typename std::enable_if<(sizeof(typename anydata_internal_::RemoveCvRef<T>::Type) > maxSize)>::type * = 0)
: functions(anydata_internal_::getAnyDataFunctions<LargeData>()), buffer() {
new (buffer.data()) LargeData(std::forward<T>(object));
}
AnyData(AnyData && other) : functions(other.functions), buffer() {
if(functions != nullptr) {
functions->moveConstruct(other.buffer.data(), buffer.data());
@ -154,12 +196,22 @@ public:
const void * getAddress() const {
assert(functions != nullptr);
return buffer.data();
if(! isLargerData()) {
return buffer.data();
}
else {
return ((const LargeData *)buffer.data())->getAddress();
}
}
template <typename T>
bool isType() const {
return anydata_internal_::getAnyDataFunctions<T>() == functions;
if(! isLargerData()) {
return anydata_internal_::getAnyDataFunctions<T>() == functions;
}
else {
return ((const LargeData *)buffer.data())->isType<T>();
}
}
template <typename T>
@ -172,6 +224,11 @@ public:
return (T *)getAddress();
}
private:
bool isLargerData() const {
return functions == anydata_internal_::getAnyDataFunctions<LargeData>();
}
private:
const anydata_internal_::AnyDataFunctions * functions;
std::array<std::uint8_t, maxSize> buffer;

View File

@ -33,6 +33,18 @@ struct EventB : Event {
int b2;
};
struct LargeEventA : Event {
int a;
std::array<void *, 100> dummy;
};
struct LargeEventB : Event {
int b1;
int b2;
std::array<void *, 100> dummy;
};
template <typename A, typename B>
void doExecuteEventQueue(
const std::string & message,
const size_t queueSize,
@ -62,8 +74,8 @@ void doExecuteEventQueue(
]{
for(size_t iterate = 0; iterate < iterateCount; ++iterate) {
for(size_t i = 0; i < queueSize; ++i) {
eventQueue.enqueue(i % eventCount, std::make_shared<EventA>());
eventQueue.enqueue(i % eventCount, std::make_shared<EventB>());
eventQueue.enqueue(i % eventCount, std::make_shared<A>());
eventQueue.enqueue(i % eventCount, std::make_shared<B>());
}
eventQueue.process();
}
@ -80,6 +92,7 @@ void doExecuteEventQueue(
;
}
template <typename A, typename B>
void doExecuteEventQueueWithAnyData(
const std::string & message,
const size_t queueSize,
@ -113,8 +126,8 @@ void doExecuteEventQueueWithAnyData(
]{
for(size_t iterate = 0; iterate < iterateCount; ++iterate) {
for(size_t i = 0; i < queueSize; ++i) {
eventQueue.enqueue(i % eventCount, EventA());
eventQueue.enqueue(i % eventCount, EventB());
eventQueue.enqueue(i % eventCount, A());
eventQueue.enqueue(i % eventCount, B());
}
eventQueue.process();
}
@ -136,7 +149,9 @@ void doExecuteEventQueueWithAnyData(
TEST_CASE("b8, EventQueue, AnyData")
{
doExecuteEventQueue("Without AnyData", 100, 1000 * 100, 100);
doExecuteEventQueueWithAnyData("With AnyData", 100, 1000 * 100, 100);
doExecuteEventQueue<EventA, EventB>("Without AnyData, small data", 100, 1000 * 100, 100);
doExecuteEventQueueWithAnyData<EventA, EventB>("With AnyData, small data", 100, 1000 * 100, 100);
doExecuteEventQueue<LargeEventA, LargeEventB>("Without AnyData, large data", 100, 1000 * 100, 100);
doExecuteEventQueueWithAnyData<LargeEventA, LargeEventB>("With AnyData, large data", 100, 1000 * 100, 100);
}

View File

@ -22,6 +22,26 @@
namespace {
template <typename T>
bool isWithinAnyData(const T & anyData, const void * address)
{
return (std::ptrdiff_t)address >= (std::ptrdiff_t)&anyData
&& (std::ptrdiff_t)address < (std::ptrdiff_t)&anyData + (std::ptrdiff_t)sizeof(T)
;
}
template <typename T>
bool isWithinAnyData(const T & anyData)
{
return isWithinAnyData(anyData, anyData.getAddress());
}
template <typename T>
bool isLargeAnyData(const T & anyData)
{
return ! isWithinAnyData(anyData);
}
TEST_CASE("AnyData, maxSizeOf")
{
REQUIRE(eventpp::maxSizeOf<
@ -40,60 +60,126 @@ TEST_CASE("AnyData, maxSizeOf")
);
}
TEST_CASE("AnyData, default")
struct LargeDataBase
{
using Data = eventpp::AnyData<64>;
eventpp::EventQueue<int, void (const Data &)> queue;
queue.appendListener(3, [](const Data & value) {
REQUIRE(value.isType<int>());
REQUIRE((long)value == 5);
REQUIRE(value.get<long>() == 5);
});
queue.enqueue(3, 5);
queue.process();
std::array<void *, 100> largeData;
};
template <typename T>
bool isLargeDataBase()
{
return sizeof(T) >= sizeof(LargeDataBase);
}
TEST_CASE("AnyData, unique_ptr")
struct DataUniquePtr
{
using Ptr = std::unique_ptr<int>;
using Data = eventpp::AnyData<sizeof(Ptr)>;
Data data(Ptr(new int(5)));
REQUIRE(data.isType<Ptr>());
REQUIRE(*data.get<Ptr>() == 5);
Data data2(Ptr(new int(5)));
REQUIRE(data2.isType<Ptr>());
REQUIRE(*data2.get<Ptr>() == 5);
Data data3(Ptr(new int(8)));
REQUIRE(data3.isType<Ptr>());
REQUIRE(*data3.get<Ptr>() == 8);
std::unique_ptr<std::string> ptr;
};
struct LargeDataUniquePtr : DataUniquePtr, LargeDataBase {};
TEMPLATE_TEST_CASE("AnyData, DataUniquePtr", "", DataUniquePtr, LargeDataUniquePtr)
{
using Type = TestType;
using MyAnyData = eventpp::AnyData<sizeof(DataUniquePtr)>;
Type data {};
data.ptr = std::unique_ptr<std::string>(new std::string("Hello"));
REQUIRE(data.ptr);
MyAnyData anyData { std::move(data) };
REQUIRE(anyData.isType<Type>());
REQUIRE(! data.ptr);
REQUIRE(isLargeAnyData(anyData) == isLargeDataBase<Type>());
REQUIRE(*anyData.get<DataUniquePtr>().ptr == "Hello");
REQUIRE(*anyData.get<Type>().ptr == "Hello");
}
TEST_CASE("AnyData, shared_ptr")
struct DataSharedPtr
{
using Ptr = std::shared_ptr<int>;
using Data = eventpp::AnyData<sizeof(Ptr)>;
Ptr ptr(std::make_shared<int>(8));
REQUIRE(ptr.use_count() == 1);
{
Data data(ptr);
REQUIRE(ptr.use_count() == 2);
REQUIRE(data.isType<Ptr>());
REQUIRE(*data.get<Ptr>() == 8);
Data data2(ptr);
REQUIRE(ptr.use_count() == 3);
REQUIRE(data2.isType<Ptr>());
REQUIRE(*data2.get<Ptr>() == 8);
REQUIRE(*data.get<Ptr>() == 8);
REQUIRE(*ptr == 8);
std::shared_ptr<std::string> ptr;
};
*ptr = 5;
REQUIRE(*data2.get<Ptr>() == 5);
REQUIRE(*data.get<Ptr>() == 5);
REQUIRE(*ptr == 5);
struct LargeDataSharedPtr : DataSharedPtr, LargeDataBase {};
REQUIRE(ptr.use_count() == 3);
TEMPLATE_TEST_CASE("AnyData, DataSharedPtr", "", DataSharedPtr, LargeDataSharedPtr)
{
using Type = TestType;
using MyAnyData = eventpp::AnyData<sizeof(DataSharedPtr)>;
Type data {};
data.ptr = std::make_shared<std::string>("Hello");
REQUIRE(data.ptr.use_count() == 1);
SECTION("copy") {
MyAnyData anyData { data };
REQUIRE(anyData.isType<Type>());
REQUIRE(data.ptr.use_count() == 2);
REQUIRE(anyData.get<DataSharedPtr>().ptr.use_count() == 2);
REQUIRE(isLargeAnyData(anyData) == isLargeDataBase<Type>());
REQUIRE(*anyData.get<DataSharedPtr>().ptr == "Hello");
REQUIRE(*anyData.get<Type>().ptr == "Hello");
*data.ptr = "world";
REQUIRE(*anyData.get<DataSharedPtr>().ptr == "world");
REQUIRE(*anyData.get<Type>().ptr == "world");
}
REQUIRE(ptr.use_count() == 1);
SECTION("move") {
MyAnyData anyData { std::move(data) };
REQUIRE(anyData.isType<Type>());
REQUIRE(data.ptr.use_count() == 0);
REQUIRE(anyData.get<DataSharedPtr>().ptr.use_count() == 1);
REQUIRE(isLargeAnyData(anyData) == isLargeDataBase<Type>());
REQUIRE(*anyData.get<DataSharedPtr>().ptr == "Hello");
REQUIRE(*anyData.get<Type>().ptr == "Hello");
}
}
struct LifeCounter
{
int ctors;
};
struct DataLifeCounter
{
explicit DataLifeCounter(LifeCounter * counter) : counter(counter) {
++counter->ctors;
}
DataLifeCounter(const DataLifeCounter & other) : counter(other.counter) {
++counter->ctors;
}
DataLifeCounter(DataLifeCounter && other) : counter(other.counter) {
++counter->ctors;
}
~DataLifeCounter() {
--counter->ctors;
}
LifeCounter * counter;
};
struct LargeDataLifeCounter : DataLifeCounter, LargeDataBase
{
explicit LargeDataLifeCounter(LifeCounter * counter) : DataLifeCounter(counter), LargeDataBase() {
}
};
TEMPLATE_TEST_CASE("AnyData, DataLifeCounter", "", DataLifeCounter, LargeDataLifeCounter)
{
using Type = TestType;
using MyAnyData = eventpp::AnyData<sizeof(DataLifeCounter)>;
LifeCounter lifeCounter {};
REQUIRE(lifeCounter.ctors == 0);
{
Type data { &lifeCounter };
REQUIRE(lifeCounter.ctors == 1);
{
MyAnyData anyData { data };
REQUIRE(anyData.isType<Type>());
REQUIRE(lifeCounter.ctors == 2);
REQUIRE(isLargeAnyData(anyData) == isLargeDataBase<Type>());
}
REQUIRE(lifeCounter.ctors == 1);
}
REQUIRE(lifeCounter.ctors == 0);
}
enum class EventType {
@ -122,6 +208,17 @@ struct EventMouse : Event {
EventMouse(const int x, const int y) : Event(EventType::mouse), x(x), y(y) {
}
};
struct LargeEventKey : EventKey, LargeDataBase {
explicit LargeEventKey(const int key) : EventKey(key), LargeDataBase() {
}
};
struct LargeEventMouse : EventMouse, LargeDataBase {
explicit LargeEventMouse(const int x, const int y) : EventMouse(x, y), LargeDataBase() {
}
};
constexpr std::size_t eventMaxSize = eventpp::maxSizeOf<
Event, EventKey, EventMouse, std::string
>();
@ -164,8 +261,8 @@ TEST_CASE("AnyData, Policies")
expectedKey = 8;
expectedX = 12345678;
expectedY = 9876532;
queue.enqueue(EventType::mouse, EventMouse(12345678, 9876532));
queue.enqueue(EventType::key, EventKey(8));
queue.enqueue(EventType::mouse, LargeEventMouse(12345678, 9876532));
queue.enqueue(EventType::key, LargeEventKey(8));
queue.process();
}