Giter VIP home page Giter VIP logo

signals's People

Contributors

j-jorge avatar thewisp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

signals's Issues

Custom PMF solver / devirtualize target member function if virtual

When a signal is bound to an object's virtual member function, we already know which exact function to call, assuming the object has finished initialization. If we could devirtualize it at this stage, there is no need to pay for the virtual while calling.

PMF structure is compiler-defined.

Moving signal can be slow

When the signal has been moved, all its connections need to be notified. Can we instead store a pointer to pointer to the signal instead?

Severe potential bug

Run the following code: the assert will fail.
This is simply because the lambda is not copied to 'sig',
hence its data destructed before sig(10); is reached.

By using std::move(lambda), the problem is solved.
But it's easy to forget this and have this bug during run time.

{
	fteng::signal<void(int)> sig;
	struct foo
	{
		~foo() { x = 999; }
		int x = 5;
	};
	int result = 0;
	{
		foo value;
		auto lambda = [&result, value](int count) { result = value.x + count; };
		sig.connect(lambda);
		//sig.connect(std::move(lambda));
	}

	sig(10);

	assert(result == 15);
}

Can't compile functor lambdas in C++20

Out of curiosity I tried to compile it with C++20 (clang 10 and gcc 9.3), but there is an error with functor lambdas. Example in Compiler Explorer:

https://godbolt.org/z/AEBAsk

The BlockingConnection namespace is just copied over from main.cpp. Compiling with -std=c++17 works as expected, but not with -std=c++20.

https://raw.githubusercontent.com/TheWisp/signals/master/signals.hpp:332:24: error: no matching constructor for initialization of 'unique'

    new (&call.object) unique{ new f_type(functor) };

                       ^     ~~~~~~~~~~~~~~~~~~~~~~~

<source>:16:32: note: in instantiation of function template specialization 'fteng::signal<void (int)>::connect<(lambda at <source>:16:40)>' requested here

    fteng::connection conn = sig.connect([&](int count) {

                                 ^

https://raw.githubusercontent.com/TheWisp/signals/master/signals.hpp:326:6: note: candidate constructor not viable: no known conversion from 'f_type *' (aka '(lambda at <source>:16:40) *') to 'unique &' for 1st argument

    unique(unique&) = delete;

    ^

Unclear design when connecting a functor object with operator()

There is no way to distinguish between a functor and a lambda.

A functor may be treated the same way as a lambda and get copied into the signal. As a result, the copy gets notified instead of the functor object. This is fine if all the members are pointer or references or the operator only causes global side-effects. However, if we rely on the data in the functor, the result is wrong.

struct stuff{
    int x = 0;
    void operator()(int y) { x+=y; }
};

stuff s;
sig.connect(s);
sig(10);
cout << s.x; //prints 0 instead of 10

On the other hand, if we treat the functor as a regular object, it may go out of bounds without disconnecting, dangling the callback. In case of a lambda, there is actually no way to save the connection into the structure for an RAII cleanup.

Not a bug, comment about the benchmark algorithm

I think the benchmark algorithm from Bench.h is unrealistic: 1 observable object and 100'000 observers connected at observable object and one call for each observer.
I tried the following benchmark algorithm: 1 observable object with 2 observer and call the observers 100'000 times, virtuals observer vs free functions.
On my laptop(CPU I7-10870H, RAM 16GB), build with MSVC 2019 x86 Release: the result wasn't good for free functions, 175-200% duration for free functions vs virtuals functions.

Your benchmark on my laptop:
Signal vs virtual (SAME class, SEQUENTIAL objects)
Signal (member func): 188 us
Signal (lambda): 194 us
Virtual call: 427 us
Signal (member func) takes 44% time of virtual function calls
Signal (lambda) takes 45% time of virtual function calls

Signal vs virtual (SAME class, RANDOM objects)
Signal (member func): 170 us
Signal (lambda): 177 us
Virtual call: 511 us
Signal (member func) takes 33% time of virtual function calls
Signal (lambda) takes 34% time of virtual function calls

Signal vs virtual (RANDOM classes, SEQUENTIAL objects)
Signal (member func): 190 us
Signal (lambda): 188 us
Virtual call: 283 us
Signal (member func) takes 67% time of virtual function calls
Signal (lambda) takes 66% time of virtual function calls

Signal vs virtual (RANDOM classes, RANDOM objects)
Signal (member func): 201 us
Signal (lambda): 192 us
Virtual call: 741 us
Signal (member func) takes 27% time of virtual function calls
Signal (lambda) takes 25% time of virtual function calls

Memory leak when the signal is destroyed before the fteng::connection is destroyed

If the signal is destroyed after the connected functor, then there is a leak.

Example:

	std::function<void()> stdFunction = [] {};

	auto* testSignal = new fteng::signal<void()>;
	fteng::connection connection = testSignal->connect(std::move(stdFunction));

	delete testSignal;

The leaked object is 'f_type'
allocated from the line:

new (&call.object) unique{ new f_type(std::move(functor)) };

inside connect(F&& functor)

Benchmark doesn’t represent typical behavior of virtual calls

Currently, the objects are constructed and stored in a vector matching their order of creations. The benchmark then calls them in random order. To make the comparison fair, we should rather shuffle the objects in storage beforehand, and call them back in linear order.

[question] Not thread safe?

As it looks like the library is not thread safe. For example one thread connects another disconnects, emits, etc.
Is this correct?

Trouble with nested connections

Safe Recursion and Modification While Iterating

Based on your README, I think the code snippet below is meant to work, but I'm encountering some kind of stack corruption or memory invalidation or something when running (it's causing allocation of a really large value (probably from uninitialized memory). I'm seeing similar behaviour in my own program which I've tried to recreate in this minimal test case.

#include <iostream>
#include <signals/signals.hpp>

int main( int /*argc*/, char** /*argv*/ )
{
    fteng::signal<void(void)> test;

    test.connect([&test](){
        std::cout << "A" << std::endl;
        test.connect([&test](){std::cout << "B" << std::endl;});
        test.connect([&test](){std::cout << "C" << std::endl;});
    });

    std::cout << "=============" << std::endl;
    test();
    std::cout << "=============" << std::endl;
    test();

    return 0;
}

Outputs:

=============
A
libc++abi: terminating with uncaught exception of type std::bad_alloc: std::bad_alloc

1  __pthread_kill                                                                                                                                                                                      (x86_64) /usr/lib/system/libsystem_kernel.dylib       0x7fff2049392e 
2  pthread_kill                                                                                                                                                                                        (x86_64) /usr/lib/system/libsystem_pthread.dylib      0x7fff204c25bd 
3  abort                                                                                                                                                                                               (x86_64) /usr/lib/system/libsystem_c.dylib            0x7fff20417411 
4  abort_message                                                                                                                                                                                       (x86_64) /usr/lib/libc++abi.dylib                     0x7fff20485ef2 
5  demangling_terminate_handler()                                                                                                                                                                      (x86_64) /usr/lib/libc++abi.dylib                     0x7fff204775e5 
6  _objc_terminate()                                                                                                                                                                                   (x86_64h) /usr/lib/libobjc.A.dylib                    0x7fff20370595 
7  std::__terminate(void ( *)())                                                                                                                                                                       (x86_64) /usr/lib/libc++abi.dylib                     0x7fff20485307 
8  __cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception *)                                                                                                                                             (x86_64) /usr/lib/libc++abi.dylib                     0x7fff20487beb 
9  __cxa_throw                                                                                                                                                                                         (x86_64) /usr/lib/libc++abi.dylib                     0x7fff20487bb2 
10 operator new(unsigned long)                                                                                                                                                                         (x86_64) /usr/lib/libc++abi.dylib                     0x7fff2048786f 
11 std::__libcpp_allocate(unsigned long, unsigned long)                                                                                                                                                new                                              253  0x1000096cd    
12 std::allocator<fteng::details::sig_base::call>::allocate(unsigned long)                                                                                                                             memory                                           1664 0x100009615    
13 std::allocator_traits<std::allocator<fteng::details::sig_base::call>>::allocate(std::allocator<fteng::details::sig_base::call>&, unsigned long)                                                     memory                                           1400 0x1000094ad    
14 std::__split_buffer<fteng::details::sig_base::call, std::allocator<fteng::details::sig_base::call>&>::__split_buffer(unsigned long, unsigned long, std::allocator<fteng::details::sig_base::call>&) __split_buffer                                   318  0x100009402    
15 std::__split_buffer<fteng::details::sig_base::call, std::allocator<fteng::details::sig_base::call>&>::__split_buffer(unsigned long, unsigned long, std::allocator<fteng::details::sig_base::call>&) __split_buffer                                   317  0x10000900d    
16 void std::vector<fteng::details::sig_base::call>::__emplace_back_slow_path<>()                                                                                                                      vector                                           1668 0x100008d39    
17 fteng::details::sig_base::call& std::vector<fteng::details::sig_base::call>::emplace_back<>()                                                                                                       vector                                           1690 0x100008b2b    
18 fteng::connection_raw fteng::signal<void ()>::connect<main::$_0::operator()() const::'lambda0'()>(main::$_0::operator()() const::'lambda0'()&&) const                                               signals.hpp                                      307  0x100009d57    
19 main::$_0::operator()() const                                                                                                                                                                       main.cpp                                         11   0x100009c0f    
20 fteng::connection_raw fteng::signal<void ()>::connect<main::$_0>(main::$_0&&) const::'lambda'(void *)::operator()(void *) const                                                                     signals.hpp                                      308  0x100009b8c    
21 fteng::connection_raw fteng::signal<void ()>::connect<main::$_0>(main::$_0&&) const::'lambda'(void *)::__invoke(void *)                                                                             signals.hpp                                      308  0x100009b65    
22 void fteng::signal<void ()>::operator()<>() const                                                                                                                                                   signals.hpp                                      229  0x100006c6a    
23 main                                                                                                                                                                                                main.cpp                                         15   0x100006948    
24 start                                                                                                                                                                                               (x86_64) /usr/lib/system/libdyld.dylib                0x7fff204ddf5d 

Originally I thought there was some issue with the scope of the lambda related to #20 but I see the same on the PR that was raised against that issue (https://github.com/mikezackles/signals/tree/issue-20) or when using a simple functor:

void method_B() { std::cout << "B" << std::endl; }
void method_C() { std::cout << "C" << std::endl; }

int main( int /*argc*/, char** /*argv*/ )
{
    fteng::signal<void(void)> test;

    test.connect([&test](){
        std::cout << "A" << std::endl;
        test.connect(method_B);
        test.connect(method_C);
    });

    std::cout << "=============" << std::endl;
    test();
    std::cout << "=============" << std::endl;
    test();

    return 0;
}

If I comment out the last connection (of the C printout) then I see what might be considered expected output:

=============
A
=============
A
B

Though depending on how the iteration is implemented I might have expected the first invocation to print A\nB and the second to print A\nB\nB (especially since the loop looks like for (size_t i = 0, n = calls.size(); i < n; ++i) in operator().

I've not been able to figure out what is going wrong - I can't see any iterators getting invalidated or something with the recursive modification...

Any thoughts appreciated!

Suggestion: using std::unique_ptr instead of raw pointer

Hello,

During code explanation I have noticed that you are using raw pointer:

blocked_connection* blocked_conn;

I can suggest you to use std::unique_ptr. It will help to avoid manual delete calls, for instance, here (RAII would be worked in this case):

virtual ~conn_base()
{
	if (!blocked)
	{
...
	}
	else
	{
		delete blocked_conn;
	}
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.