linhaigoo / SigSlot

Just Like QT

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Use SigSlot

中文 || English

if you have any question,you can try to touch me: ykikoykikoykiko@gmail.com

only support c++17 and above,

include SigSlot.hpp to use

Example

class Window 
{  
public:  
    //use Event to define a event
    //template parameter is the type of the parameter of the callback function
    SigSlot::Event<int, char, std::string> event;  
};

class Button
{  
    int id;
public:  
    Button(int value):id(value){}
    void OnClick(int a, char b, std::string c)  
    {  
        std::cout << "ID is" << id << "Parameters are" << a << " " << b << " " << c << std::endl;  
    }  
};
int main()  
{  
    Window window;  
    Button button(3);
    //connect the event and the callback function
    //template<typename T, typename U, typename ...SignalArgs, typename ...SlotArgs>
    //void connect(T *sender, Event<SignalArgs...> T::* event, U *receiver, void(U::* func)(SlotArgs...), SIGSLOT type = SIGSLOT::AutoConnection);
    //there are five parameters, the first is the sender, the second is the event, the third is the receiver, 
    //the fourth is the function to bind, the fifth is the connection type (we will talk about it later)
    
    SigSlot::connect(&window, &Window::event, &button, &Button::OnClick);  
    //emit the event, trigger all the callback function
    window.event.emit(1, 'a', "hello");  
    return 0;  
    
}

It seems that the callback function is simpler than the event that we use, let's continue to see

class Label 
{  
public:  
    void TextChanged(std::string text)  
    {  
        std::cout << "Label" << text << std::endl;  
    }  
};

int main()  
{  
    Window window;  
    Button button;  
    Label label;
    //support one to many, many to one connection
    SigSlot::connect(&window, &Window::event, &button, &Button::OnClick);
    
    //support the parameter of the callback function is a subset of the event
    //the extra parameter will be cut off automatically
    SigSlot::connect(&window, &Window::event, &label, &Label::TextChanged);  
    window.event.emit(1, 'a', "hello");  
    //result
    //Label hello
    //ID is 1,Parameters are 1 a hello
    //the order of the callback function is not guaranteed because we use unordered_map to implement it
    
    //support disconnect
    SigSlot::disconnect(&window, &Window::event, &label, &Label::TextChanged);  
    window.event.emit(1, 'a', "hello");
	
    //support lambda expression
    SigSlot::connect(&window,&Window::event, &label,[&](std::string text)  
    {  
	std::cout << "Label " << text << std::endl;  
    });
    
    //lambda can't be disconnected
    window.event.emit(1, 'a', "hello");
	
    //support global function
    //void Print(int a, char b)  
    //{  
    //    std::cout << "Print " << a << " " << b << " " << std::endl;  
    //}
    SigSlot::connect(window, &Window::event, Print);  
    std::cout << "------------------------\n";  
    window->event.emit(3, 'a', "func");  
    SigSlot::disconnect(window, &Window::event, Print);
    return 0;  
}

Disconnect the connection automatically when the object is destroyed

//solve the problem that the object is destroyed but the connection is not disconnected
int main()  
{  
    Window *window = new Window();  
    Button *button = new Button(3);  
    Label *label = new Label();  
    SigSlot::connect(window, &Window::event, button, &Button::OnClick);  
    SigSlot::connect(window, &Window::event, label, &Label::TextChanged);  
    SigSlot::connect(window, &Window::event, button, [](int a, std::string c)  
    {  
        std::cout << "Lambda " << a << " " << " " << c << std::endl;  
    });  
    window->event.emit(1, 'a', "hello");  
    //Lambda 1  hello
    //Label hello
    //ID is 1, Parameters are 1 a hello
    delete button;  
    window->event.emit(2, 'b', "world");
    //Lambda 2  world
    //Label world
    //ID is -778816336 Parameters are 2 b world  
    //You can see that the callback function of the deleted object is still called
    //This is a very unsafe behavior, access uninitialized memory

    //to avoid this problem, you need disconnect the connection before the object is destroyed 
    //just like this:SigSlot::disconnect(window, &Window::event, button, &Button::OnClick);  
    delete window;  
    delete label;  
    return 0;  
}
//It is very troublesome to disconnect the connection manually
//So we provide two ways to disconnect the connection automatically when the object is destroyed
1:inherit the class from SigSlot::Object
class Button :public SigSlot::Object
{  
private:  
    int id;  
public:  
    Button(int value):id(value){}  
    void OnClick(int a, char b, std::string c)  
    {  
        std::cout << "ID is " << id << "Parameters are" << a << " " << b << " " << c << std::endl;  
    }  
};
2:add the macro SIGSLOT_OBJECT to the class
class Button 
{ 
    SIGSLOT_OBJECT 
private:  
    int id;  
public:  
    Button(int value):id(value){}  
    void OnClick(int a, char b, std::string c)  
    {  
        std::cout << "ID is " << id << "Parameters are" << a << " " << b << " " << c << std::endl;  
    }  
};
//you can't use both of them at the same time
//you can't connect a object that is not inherited from Object or add the macro SIGSLOT_OBJECT to a object
//the compiler will report an error
//"Sender and Receiver must both be Object or neither be"

//now let's see the result
//Lambda 1  hello
//Label hello
//Id is 1 Parameters are 1 a hello

//you can see that the callback function of the deleted object is not called
//Label world

Multi-threaded support

//only Object can use this feature, that is, inherit from Object or add the macro SIGSLOT_OBJECT
template<typename ...Args>
void Print(Args&&... args)
{
    std::string str{};
    constexpr auto f = [&](auto arg)
    {
        if constexpr (std::is_same_v<decltype(arg), int>)
        {
            str += std::to_string(arg);
        }
        else if constexpr (std::is_same_v<decltype(arg), std::thread::id>)
        {
            std::stringstream ss{};
            ss << arg;
	    str += ss.str();
	}
        else
        {
            str += arg;
        }
    };
    (f(args), ...);
    std::cout << str;
}
class Window 
{  
    SIGSLOT_OBJECT
public:  
    SigSlot::Event<int, char, std::string> event;  
};  
  
class Button: public SigSlot::Object  
{  
public:  
    void OnClick(int a, char b, std::string c)  
    {  
        Print("Button ", a, " ", b, " ", c," this thread Id is: ", std::this_thread::get_id(),"\n");  
    }  
    void Show(int a) const  
    {  
	Print("Button ", a, " this thread Id is: ", std::this_thread::get_id(), "\n");  
    }
};  
class Label 
{  
    SIGSLOT_OBJECT
public:  
    void TextChanged(std::string text)  
    {  
        Print("Label ", text," this thread Id is: ", std::this_thread::get_id(),"\n");  
    }  
};  
  
int main()  
{  
    std::thread t1([]()  
    {  
       //start the event loop in the thread
       SigSlot::EventLoop loop;  
       loop.Run();
       //to work with other event loop, you can use this code
       //while(true)
       //{
       //	loop.Update();
       // } 
       //in this way, you can handler the SigSlot event in  event loop of other library
    });  
  
    std::this_thread::sleep_for(std::chrono::seconds(1));  
    Window *window = new Window();  
    Button *button = new Button();  
    Label *label = new Label(); 
    //move to button to thread t1, then the callback function of button will be executed in thread t1
    button->MoveToThread(t1);  
    SigSlot::connect(window, &Window::event, button, &Button::OnClick);  
    SigSlot::connect(window, &Window::event, label, &Label::TextChanged);  
    SigSlot::connect(window, &Window::event, button, [](int a, std::string c)  
    {  
        Print("Button ", a, " ", c," this thread Id is: ", std::this_thread::get_id(),"\n");  
    });  
    std::cout << "------------------------\n";  
    window->event.emit(1, 'a', "hello");  
    std::this_thread::sleep_for(std::chrono::seconds(2));  
    delete button;  
    delete window;  
    delete label;  
    t1.detach();  
    return 0;  
    //result:
    //------------------------
    //Label hello  this thread Id is: 1
    //Button 1 hello  this thread Id is: 2
    //Button 1 a hello  this thread Id is: 2
}

you can see that the callback function works in the object's thread

Connection Type

in fact connect has fifth parameter, it is the connection type
```c++
enum class SIGSLOT  
{  
    AutoConnection,  
    DirectConnection,  
    QueuedConnection,  
    BlockingQueuedConnection,  
};

there are four possible values, first of all, it is default to the current thread when the object is created, and MoveToThread can be used to move to another thread, DirectConnection means that the function is executed directly in the current thread when the event is triggered, that is, the style of a common callback function, QueuedConnection means that when the event is triggered, the function is packaged as a task and submitted to the event loop of the thread to which the object belongs. Each event loop has a message queue to store messages. When there are messages in the message queue, the messages will be removed and executed continuously. If there are no messages, the thread will be suspended and wait for the wake-up. It can be found that QueuedConnection can achieve asynchronous programming. If you need to do some time-consuming work, just submit the task to the target thread. And because the event loop is single-threaded, the tasks will be executed in order, so there is no need to worry about the thread safety of the object. BlockingQueuedConnection is similar to QueuedConnection, but it will block the current thread until the task is executed. If you need to get the return value of the callback function, you can use BlockingQueuedConnection. If you don't need to get the return value, you can use QueuedConnection to improve performance. If you don't need to move the object to another thread, you can use DirectConnection, which is the fastest way to connect signals and slots. If you need to move the object to another thread, you can use QueuedConnection, which is the safest way to connect signals and slots.

the default connection type is AutoConnection, it will be DirectConnection if the object is in the current thread, otherwise it will be QueuedConnection

Cast event to EventLoop

EventLoop *GetEventLoop(std::thread::id id = std::this_thread::get_id())
//you can get the event loop of the target thread by this function
loop->PostEvent(std::function<void()>&& func);
loop->SendEvent(std::function<void()>&& func);
// you can post or send a task to the event loop
//post means that the task will be executed asynchronously, and send means that the task will be executed synchronously
loop->Quit();
//quit the event loop
//because the std thread library may continue to execute before the thread is initialized, 
// so the event loop may not be created when the event is triggered, 
// and the detach will get an empty value, so I encapsulated the C++ thread, you can use it like this
int main()  
{  
    SigSlot::Thread t1([]()  
    {  
        SigSlot::EventLoop loop;  
        loop.Run();  
    });  
    SigSlot::Thread t2([]()  
     {  
         SigSlot::EventLoop loop;  
         while(true)
         {
	     loop.Update();
         } 
     });  
    Window *window = new Window();  
    Button *button = new Button();  
    Label *label = new Label();  
    button->MoveToThread(t1);  
    label->MoveToThread(t2);  
    SigSlot::connect(window, &Window::event, button, &Button::Show,SigSlot::SIGSLOT::DirectConnection);  
    SigSlot::connect(window, &Window::event, button, &Button::OnClick,SigSlot::SIGSLOT::QueuedConnection);  
    SigSlot::connect(window, &Window::event, label, &Label::TextChanged,SigSlot::SIGSLOT::BlockingQueuedConnection);  
    std::cout << "------------------------\n";  
    window->event.emit(1, 'a', "hello");  
  
    delete button;  
    delete window;  
    delete label;  
    return 0;  
}
//result
//Button 1  this thread Id is: 1
//Button 1 a hello  this thread Id is: 2
//Label hello  this thread Id is: 3

About

Just Like QT

License:MIT License


Languages

Language:C++ 100.0%