17 Commits

Author SHA1 Message Date
140b585854 Refactor to a single-header so you don't run into that psychotic linkage error because you forgot to include the other header. :) 2024-08-22 18:49:02 -04:00
217fe1df88 Truncate results for easy read! 2024-08-21 16:49:22 -04:00
00b3857647 speed tests 2024-08-21 16:39:51 -04:00
d85173ccd9 Test MSVC support 2024-08-21 15:47:58 -04:00
5e3e20a7e5 Some documentation work 2024-08-21 15:38:42 -04:00
69e245632f Merge remote-tracking branch 'origin/master' 2024-08-21 15:37:15 -04:00
e7f3f1ff95 Write README file 2024-08-21 15:37:10 -04:00
d0222a0dce Did some hackypooos for Jtest 3 2024-08-21 14:44:46 -04:00
f8310f7bed Did some hackypooos for Jtest 2 2024-08-21 14:35:37 -04:00
1290afdc68 Did some hackypooos for Jtest 2024-08-21 14:29:52 -04:00
5a1d864e15 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	CMakeLists.txt
2024-08-21 13:22:37 -04:00
e01acbed26 Refactor Event class to support any delegate type. 2024-08-21 13:21:57 -04:00
27af86ebf2 Update Event.h 2024-06-01 19:32:26 -04:00
Redacted
7620b9f06f Update main.cpp
Fix oof
2024-05-21 13:45:25 -04:00
d9e0931a4d Update CMakeLists.txt 2024-05-21 13:40:35 -04:00
576d51ac68 Windows main statement 2024-05-13 21:09:21 -04:00
ccf9edee93 Update CMakeLists.txt
Version downgrade
2024-02-24 07:46:40 -05:00
8 changed files with 325 additions and 90 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/cmake-build-debug
/.idea

View File

@@ -10,20 +10,19 @@ endif()
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
if (WIN32)
set(CMAKE_CXX_FLAGS "-municode")
endif()
file(GLOB_RECURSE SOURCES "src/*.cpp") file(GLOB_RECURSE SOURCES "src/*.cpp")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include_directories("include") include_directories("include")
add_library(Event SHARED ${SOURCES} if (UNIX)
src/Event.cpp add_library(Event SHARED ${SOURCES})
include/EventConnection.h endif()
src/EventConnection.cpp)
if (WIN32)
add_library(Event STATIC ${SOURCES})
endif()
set_target_properties(Event PROPERTIES LINKER_LANGUAGE CXX) set_target_properties(Event PROPERTIES LINKER_LANGUAGE CXX)

View File

@@ -0,0 +1,98 @@
# Event
### A C++20 Event system inspired by C# syntax.
![](https://img.shields.io/badge/Redacted-Software-black)
![Static Badge](https://img.shields.io/badge/Lit-Based-%20)
![C++](https://img.shields.io/badge/C%2B%2B-20-blue)
[![License: Unlicense](https://img.shields.io/badge/license-Unlicense-blue)](http://unlicense.org/)
## Features
* Fast
* (TODO: Profile!)
* Flexible
* Event delegate can be of any ![FunctionObject](https://en.cppreference.com/w/cpp/named_req/FunctionObject) type.
* Supports any set of parameters via templates.
* Easy To Use
* Written in portable and standard C++, no hacks or quirks.
* Utilizes Modern C++ features for elegant design.
* Zero dependencies. Fully self-contained.
* Unrestricted
* No GNU Copyleft communism. Totally Free & Public Domain.
## Coming Soon
* Event Filters / Policy
* Asynchronous
* Thread-Safe Version
* Priority Queue
* Auto disconnecting
## Supported Compilers
* GCC 14.2.1 (RedHat)
* MSVC 19.40
## Usage
This project is designed to be integrated via [CMake Package Manager](https://github.com/cpm-cmake/CPM.cmake).
```cmake
CPMAddPackage(
NAME Event
URL https://git.redacted.cc/josh/Event/archive/Release-7.zip
)
```
This snippet will automatically download a stable release of Event into your project's build directory, so it won't clutter your sources.
Take note of the release version and make sure it's the latest available in the Releases tab above.
```cmake
target_include_directories(${PROJECT_NAME} PUBLIC ${Event_SOURCE_DIR}/include)
target_link_libraries(${PROJECT_NAME} PUBLIC Event)
```
This snippet will import the headers, and then link the library to your library/executable. You should now be able to use Event.
The C++ snippet below reads input from the terminal and fires an event that received the input message.
```cpp
#include <Event.h>
#include <EventConnection.h>
void ReadMessage(const std::string& message) {
std::cout << "Received: " << message << std::endl;
}
int main()
{
Event<std::string> OnInput; // This and the declaration below are equivalent.
BasicEvent<std::function<void(std::string)>, std::string> OnInput2;
bool running = true;
while (running) {
std::string input;
std::cin >> input;
OnInput.Invoke(input);
OnInput(input); // Call operator is defined as well.
}
return 0;
}
More advanced examples can be found in the main.cpp file.
```
## Documentation
Documentation is automatically generated from latest commit and is hosted at https://doc.redacted.cc/event .
## Contributing
Contributions to this project are welcome! If you find a bug, have a feature request, or would like to contribute code, please submit an issue or pull request to the GitHub repository.
## License
Event is expressly licensed to the Public Domain under the Unlicense. See the LICENSE file for details.
## Acknowledgements
Event is developed and maintained by Joshua O'Leary @ Redacted Software, and contributors.
Special thanks to Maxine Hayes.

View File

@@ -1,62 +1,111 @@
/// @file Event.hpp /// @file BasicEvent.hpp
/// @description Templated Event Hook Class modeled after C# events /// @description Templated BasicEvent Hook Class modeled after C# events
/// @author Josh O'Leary - Redacted Software /// @author Josh O'Leary - Redacted Software
/// @revision 3 /// @revision 3
/// @lastedit 2024-02-21 /// @lastedit 2024-06-01
/// @license Unlicense - Public Domain /// @license Unlicense - Public Domain
#pragma once #pragma once
#include <chrono> #include <chrono>
#include <functional> #include <functional>
#include <memory>
// TODO: Document & Explain this // TODO: ThreadSafeEvent / AsyncEvent version of the class.
// TODO: ConsumableEvent - a version that can be "consumed" by a callback by returning true. It will be sunk and not passed to further callbacks.
#include <EventConnection.h> /// See EventConnection.h
template <typename delegate, typename ... Args>
class Connection;
template <typename ... Args> /// @see Event.h
class EventConnection; template <typename delegate, typename ... Args>
class BasicEvent;
template <typename ... Args> /// A type that represents a handle to an active event connection.
class Event { template <typename delegate, typename ... Args>
class Connection {
public: public:
using delegate = std::function<void(Args...)>; friend BasicEvent<delegate, Args...>;
using connection = EventConnection<Args ...>; public:
Connection(BasicEvent<delegate, Args...> *creator, delegate cb);
bool Disconnect(); // Breaks the event connection, but does not destroy the instance
void Invoke(Args... e);
public:
// Fuck it make this public
// Don't be stupid!!
delegate callback;
private:
BasicEvent<delegate, Args...> * owner;
//delegate callback;
bool active = true;
};
template<typename delegate, typename... Args>
Connection<delegate, Args...>::Connection(BasicEvent<delegate, Args...> *creator, delegate cb) : owner(creator), callback(std::move(cb)) {}
template <typename delegate, typename... Args>
void Connection<delegate, Args...>::Invoke(Args... e) { callback(e...); }
template <typename delegate, typename ... Args>
bool Connection<delegate, Args...>::Disconnect() {
if (active) {
owner->Disconnect(this);
active = false;
return true;
}
return false;
}
/// BasicEvent is a templated class that can be used to raise events that can be listened in on
/// at other points in a codebase without having to tightly couple the two pieces of code.
/// @param delegate A type that can be invoked via operator() 'Call operator'.
/// @param Args... The arguments that will be expected by this event and passed to it's delegate.
template <typename delegate, typename ... Args>
class BasicEvent {
public:
using connection = Connection<delegate, Args ...>;
using event_ptr = std::shared_ptr<connection>; using event_ptr = std::shared_ptr<connection>;
public: public:
void Await(Args& ... arg); /// Invokes the event, calling all currently bound connections, and passing in the given arguments.
void Invoke(Args... args); virtual void Invoke(Args... args);
/// The call operator also invokes the event
void operator()(Args... args); void operator()(Args... args);
connection Connect(delegate callback); connection Connect(delegate callback);
void Disconnect(connection &conn); void Disconnect(connection &conn);
connection operator+=(delegate callback); connection operator+=(delegate callback);
private: protected:
std::vector<event_ptr> listeners; std::vector<event_ptr> listeners;
uint64_t listenerCounter = 0; //uint64_t listenerCounter = 0;
}; };
template<typename... Args> template<typename delegate, typename... Args>
EventConnection<Args...> Event<Args...>::operator+=(Event::delegate callback) { return Connect(callback); } Connection<delegate, Args...> BasicEvent<delegate, Args...>::operator+=(delegate callback) { return Connect(callback); }
template<typename... Args> template<typename delegate, typename... Args>
void Event<Args...>::operator()(Args... args) { Invoke(args...);} void BasicEvent<delegate, Args...>::operator()(Args... args) { Invoke(args...);}
template <typename ... Args> template <typename delegate, typename ... Args>
void Event<Args...>::Invoke(Args... args) { void BasicEvent<delegate, Args...>::Invoke(Args... args) {
for (event_ptr &connection_ptr: this->listeners) { for (event_ptr &connection_ptr: this->listeners)
connection_ptr->Invoke(args...); connection_ptr->Invoke(args...);
} }
}
template <typename ... Args> template <typename delegate, typename ... Args>
EventConnection<Args...> Event<Args...>::Connect(delegate callback) Connection<delegate, Args...> BasicEvent<delegate, Args...>::Connect(delegate callback)
{ {
event_ptr retval(new connection(this, callback)); event_ptr retval(new connection(this, callback));
this->listeners.push_back(retval); this->listeners.push_back(retval);
return *retval; return *retval;
} }
template <typename ... Args> template <typename delegate, typename ... Args>
void Event<Args...>::Disconnect(connection &conn) { void BasicEvent<delegate, Args...>::Disconnect(connection &conn) {
listeners.erase(std::remove(listeners.begin(), listeners.end(), 99), listeners.end()); listeners.erase(std::remove(listeners.begin(), listeners.end(), 99), listeners.end());
} }
/// The default event which uses a std::function as the delegate.
template <typename ... Args>
using Event = BasicEvent<std::function<void(Args...)>, Args...>;

View File

@@ -1,45 +0,0 @@
/// @file EventConnection.hpp
/// @description Callback handler for event connections
/// @author Josh O'Leary - Redacted Software
/// @revision 3
/// @lastedit 2024-02-21
/// @license Unlicense - Public Domain
#pragma once
#include <functional>
#include <Event.h>
template <typename ... Args>
class Event;
template <typename ... Args>
class EventConnection {
private:
using delegate = std::function<void(Args...)>;
public:
EventConnection(Event<Args...> *creator, delegate cb);
bool Disconnect(); // Breaks the event connection, but does not destroy the instance
void Invoke(Args... e);
private:
Event<Args...> * owner;
delegate callback;
bool active = true;
};
template<typename... Args>
EventConnection<Args...>::EventConnection(Event<Args...> *creator, EventConnection::delegate cb) : owner(creator), callback(std::move(cb)) {}
template <typename... Args>
void EventConnection<Args...>::Invoke(Args... e) { callback(e...); }
template <typename ... Args>
bool EventConnection<Args...>::Disconnect() {
if (active) {
owner->Disconnect(this);
active = false;
return true;
}
return false;
}

145
main.cpp
View File

@@ -1,30 +1,161 @@
#include <iostream> #include <iostream>
#include <functional>
#include <Event.h> #include <Event.h>
#include <cmath>
void ProcessMessage(const std::string& message) void ProcessMessage(const std::string& message)
{ {
std::cout << "Received: " << message << std::endl; //std::cout << "Received: " << message << std::endl;
}
int SigFigsTable[] = {0,0,0,1,0,0,1,0,0,1};
int DivBy[] = {1,1,1, 1000,1000,1000, 1000000, 1000000, 1000000, 1000000000, 1000000000,1000000000};
std::vector<std::string> Suffixes = {
"", "", "",
" Thousand", " Thousand", " Thousand",
" Million", " Million", " Million",
" Billion", " Billion", " Billion",
" Trillion", " Trillion", " Trillion",
" Quadrillion", " Quadrillion", " Quadrillion"
};
float Round(float f, float decimalPlaces) {
float mult = std::pow(10, decimalPlaces);
return std::floor(f * mult + 0.5f) / mult;
}
float Sign(float f) { return f >= 0.f ? 1.f : -1.f;}
std::string Truncate(float input) {
std::stringstream ss;
std::string str = "";
if (input < 1000)
ss << std::fixed << std::setprecision(0) << input;
else {
int figs = std::ceil(std::log10(input)) - 1;
auto suffix = Suffixes[figs];
auto roundTo = SigFigsTable[figs];
auto divBy = DivBy[figs];
auto fractional = input / (float)divBy;
// Increment roundTo for extra precision!!
ss << std::fixed << std::setprecision(roundTo) << fractional << suffix;
}
str = ss.str();
return str;
} }
int main() { int main() {
Event<std::string> OnMessage; Event<int> very_simple_event;
bool run = true; very_simple_event += [] (int i) { std::cout << i+5 << std::endl; };
very_simple_event.Invoke(69);
//Const string reference
BasicEvent<std::function<void(const std::string&)>, const std::string&> OnMessage;
auto handler = OnMessage += [&] (const std::string& message) auto handler = OnMessage += [&] (const std::string& message)
{ {
std::cout << "GOTS A MESSAGE: " << message << std::endl; //std::cout << "GOTS A MESSAGE: " << message << std::endl;
}; };
//void
BasicEvent<std::function<void()>> VoidDelegate;
auto handler2 = OnMessage += ProcessMessage; auto handler2 = OnMessage += ProcessMessage;
//Float
BasicEvent<std::function<void(float)>, float> floatEvent;
auto floatHandler = floatEvent += [&] (float){};
//long long
BasicEvent<std::function<void(unsigned long long)>, unsigned long long> longlongEvent;
auto longlongHandler = longlongEvent += [&] (unsigned long long){};
bool run = true;
auto start = std::chrono::high_resolution_clock::now();
unsigned long long iterations = 0;
while (run) while (run)
{ {
std::string input; std::string input = "test";
std::cin >> input;
OnMessage.Invoke(input); OnMessage.Invoke(input);
OnMessage(input);
iterations++;
auto now = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = now - start;
if (elapsed.count() >= 1.0)
break;
} }
std::cout << "std::string& iterations in 1 second: " << Truncate(iterations) << std::endl;
start = std::chrono::high_resolution_clock::now();
iterations = 0;
while (run)
{
VoidDelegate.Invoke();
VoidDelegate();
iterations++;
auto now = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = now - start;
if (elapsed.count() >= 1.0)
break;
}
std::cout << "void iterations in 1 second: " << Truncate(iterations) << std::endl;
start = std::chrono::high_resolution_clock::now();
iterations = 0;
while (run)
{
floatEvent.Invoke(1.25);
floatEvent(1.25);
iterations++;
auto now = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = now - start;
if (elapsed.count() >= 1.0)
break;
}
std::cout << "float iterations in 1 second: " << Truncate(iterations) << std::endl;
start = std::chrono::high_resolution_clock::now();
iterations = 0;
while (run)
{
longlongEvent.Invoke(20);
longlongEvent(20);
iterations++;
auto now = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = now - start;
if (elapsed.count() >= 1.0)
break;
}
std::cout << "long long iterations in 1 second: " << Truncate(iterations) << std::endl;
return 0; return 0;
} }
#ifdef _WIN32
extern "C" {
int wmain(int argc, wchar_t* argv[]) {
return main();
}
}
#endif

View File

@@ -1 +1,3 @@
#include <Event.h> #include <Event.h>

View File

@@ -1 +0,0 @@
#include <EventConnection.h>