forked from cheng/wallet
Finally figured out how to do callbacks for my async
Can make callbacks that can access the full lambda machinery, while the same machinery is still usable in C and from C, and thus in rust and lua, and from rust and lua. modified: docs/libraries/cpp_automatic_memory_management.md The magic univeral lowest denominator C code, which can be used by and make use of, higher level language constructs such as lambdas, being (**p)(p, ptrEvent); Where p points at or into a blob that contains a pointer to C calling convention code that knows how the blob is structured. allowing us to structure it arbitarily, apart from information that the event dispatcher needs to know.
This commit is contained in:
parent
4678fba3ce
commit
fe3015b851
@ -272,6 +272,10 @@ Its pacman name is mingw-w64-dht, but it has repos all over the plac under its o
|
|||||||
It is async, driven by being called on a timer, and called when
|
It is async, driven by being called on a timer, and called when
|
||||||
data arrives. It contains a simple example program, that enables you to publish any data you like.
|
data arrives. It contains a simple example program, that enables you to publish any data you like.
|
||||||
|
|
||||||
|
## nghttp3 and lsquic
|
||||||
|
|
||||||
|
http3 libraries built on udp. Designed for async use -- build your own event loop.
|
||||||
|
They supply two priority queues for the event loop.
|
||||||
|
|
||||||
## libp2p
|
## libp2p
|
||||||
|
|
||||||
|
@ -418,14 +418,40 @@ except default and deleted constructors
|
|||||||
A lambda is a nameless value of a nameless class that is a
|
A lambda is a nameless value of a nameless class that is a
|
||||||
functor, which is to say, has `operator()` defined.
|
functor, which is to say, has `operator()` defined.
|
||||||
|
|
||||||
|
That each lambda is a unique and nameless class allows the compiler
|
||||||
|
to optimise it away, so that it becomes inline code.
|
||||||
|
|
||||||
|
So doing the kind of stuff with a lambda you would ordinarily need
|
||||||
|
a class name to do is likely to break stuff or lead to weirdness.
|
||||||
|
|
||||||
|
It looks like you are defining a real class, but your compiler does
|
||||||
|
not want to generate a real class except as a last resort.
|
||||||
|
|
||||||
But, of course you can get the class with `decltype`
|
But, of course you can get the class with `decltype`
|
||||||
and assign that nameless value to an `auto` variable,
|
and assign that nameless value to an `auto` variable,
|
||||||
or stash it on the heap with `new`,
|
and all that, but because the design for lambdas was
|
||||||
or in preallocated memory with placement `new`
|
to allow them to be efficiently converted to straightforward
|
||||||
|
code this may well result in strange complications and mysterious
|
||||||
|
syntax and semantic errors.
|
||||||
|
|
||||||
|
To tell the compiler to actually use the lambda as a lambda, and
|
||||||
|
not do all this behind the scenes cleverness, you use `std::function`
|
||||||
|
which is a regular templated class that defines `operator ()`, and if
|
||||||
|
it is defined in terms of lambda, stashes that lambda on the heap
|
||||||
|
after the style of `std::string`
|
||||||
|
|
||||||
|
However for very small lambdas (not capturing any variables, or capturing
|
||||||
|
on one variably by value, this can get optimised away), and under
|
||||||
|
the hood it does things C style.
|
||||||
|
|
||||||
|
However, depending on the compiler, `std::function` may do hidden heap
|
||||||
|
allocation.
|
||||||
|
|
||||||
But if you are doing all that, might as well explicitly define a
|
But if you are doing all that, might as well explicitly define a
|
||||||
named functor class.
|
named functor class.
|
||||||
|
|
||||||
|
## lambda on the heap
|
||||||
|
|
||||||
To construct a lambda in the heap:
|
To construct a lambda in the heap:
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
@ -459,12 +485,54 @@ This way, lambdaPtr is a pointer to a std::function that can store any callable
|
|||||||
|
|
||||||
similarly placement `new`, and `unique_ptr`.
|
similarly placement `new`, and `unique_ptr`.
|
||||||
|
|
||||||
|
Trouble is that an std::function object is a fixed sized object, like an `std::string`, typically sixteen bytes. which like an `std::string` points to a dynamically allocated object on the heap.
|
||||||
|
|
||||||
## callbacks
|
## callbacks
|
||||||
|
|
||||||
In C, a callback is implemented as an ordinary function pointer, and a pointer to void, which is then cast to a data
|
In C, a callback is implemented as an ordinary function pointer, and a pointer to void,
|
||||||
structure of the desired type.
|
which is then cast to a data structure of the desired type.
|
||||||
|
|
||||||
What the heavy C++ machinery of `std::function` does is bind the two together.
|
What the heavy C++ machinery of `std::function` does is bind the two together and then
|
||||||
|
do memory management after the fashion of `std::string`.
|
||||||
|
|
||||||
|
[compiler explorer]:https://godbolt.org/ {target="_blank"}
|
||||||
|
|
||||||
|
And `std::function`, used correctly, should compile to the identical code
|
||||||
|
merely wrapping the function pointer and the void pointer in a single struct
|
||||||
|
-- but you had better use [compiler explorer] to make sure
|
||||||
|
that you are using it correctly.
|
||||||
|
|
||||||
|
Write a callback in C, an an std::function in c++, and make sure that
|
||||||
|
the compiler generates what it should.
|
||||||
|
|
||||||
|
Ownership is going to be complicated -- since after createing and passing a callback, we
|
||||||
|
probably do not want ownership any more -- the thread is going to return
|
||||||
|
and be applied to some entirely different task. So the call that is passed the callback
|
||||||
|
as an argument by reference uses `move` to ensure that when the `std::function`
|
||||||
|
stack value in its caller pointing to the heap gets destroyed, it does not
|
||||||
|
free the value on the heap, and then stashes the moved `std::function` in some
|
||||||
|
safe place.
|
||||||
|
|
||||||
|
Another issue is that rust, python, and all the rest cannot talk to C++, they can only
|
||||||
|
talk C. On the other hand, the compiler will probably optimise the `std::function` that
|
||||||
|
consists of a lamda that is a call to function pointer and that captures a pointer to void.
|
||||||
|
|
||||||
|
Again, since compiler is designed for arcane optimization issues, have to see what happens
|
||||||
|
in [compiler explorer].
|
||||||
|
|
||||||
|
But rather than guessing about the compiler correctly guessing intent, make the
|
||||||
|
callback a C union type implementing std variant in C, being a union of `std:monostate`
|
||||||
|
a C callback taking no arguments, a C++ callback taking no arguments, C and C++ callbacks
|
||||||
|
taking a void pointer argument, a c++ callback that is a pointer to method, and
|
||||||
|
a C++ callback that is an `std::function`
|
||||||
|
|
||||||
|
In the old win32 apis, which were designed for C, and then retrofitted for C++
|
||||||
|
they would have a static member function that took an LPARAM, which was a pointer
|
||||||
|
to void pointing at the actual object, and then the static member function
|
||||||
|
would directly call the appropriate, usually virtual, actual member function.
|
||||||
|
|
||||||
|
Member function pointers have syntax that no one can wrap their brains around
|
||||||
|
so people wrap them in layers of typedefs.
|
||||||
|
|
||||||
Sometimes you want to have indefinitely many data structures, which are dynamically allocated
|
Sometimes you want to have indefinitely many data structures, which are dynamically allocated
|
||||||
and then discarded.
|
and then discarded.
|
||||||
@ -477,6 +545,57 @@ In one case, you would allocate the object every time, and when does with it, di
|
|||||||
In the other case it would be a member variable of struct that hangs around and is continually
|
In the other case it would be a member variable of struct that hangs around and is continually
|
||||||
re-used.
|
re-used.
|
||||||
|
|
||||||
|
### C compatibility
|
||||||
|
|
||||||
|
Bind the two together in a way that C can understand:
|
||||||
|
|
||||||
|
The code that calls the callback knows nothing about how the blob is structured.
|
||||||
|
The event management code knows nothing about how the blob is structured.
|
||||||
|
But the function pointer in the blob *does* know how the blob is structured.
|
||||||
|
|
||||||
|
```C
|
||||||
|
// p points to or into a blob of data containing a pointer to the callback
|
||||||
|
// and the data that the callback needs is in a position relative to the pointer
|
||||||
|
// that is known to the callback function.
|
||||||
|
enum event_type { monovalue, reply, timeout, unreachable, unexpected_error };
|
||||||
|
|
||||||
|
struct callback;
|
||||||
|
typedef extern "C" void (*callback_)(callback * pp, event_type evtype, void* event);
|
||||||
|
struct callback
|
||||||
|
{
|
||||||
|
callback_ p;
|
||||||
|
};
|
||||||
|
|
||||||
|
callback * pp;
|
||||||
|
|
||||||
|
// Within the actual function in the event handling code,
|
||||||
|
// one has to cast `callback* pp` from its base type to its actual type
|
||||||
|
// that has the rest of the data, which the event despatcher code knows nothing of.
|
||||||
|
// The event despatch code should not include the headers of the event handling code,
|
||||||
|
// as this would make possible breach of separation of responsibilities.
|
||||||
|
try{
|
||||||
|
(*((*pp).p))(pp, evtype, event );
|
||||||
|
}
|
||||||
|
catch(...){
|
||||||
|
// log error and release event handler.
|
||||||
|
// if an exception propagates from the event handling code into the event despatch code
|
||||||
|
// it is programming error, a violation of separation of responsibilities
|
||||||
|
// and the event despatch code cannot do anything with the error.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`pp` points into a blob that containst the data needed for handling the event when will happen,
|
||||||
|
and a pointer to the code that will handle it when it happens, ptrEvent is a ptr to a
|
||||||
|
struct containing an index that will tell us what kind of struct it is. It is a C union
|
||||||
|
of very different structs. Since it is dynamicaly allocated, we don't waste space, we create
|
||||||
|
the particular struct, cast it to the union, and then cast the union to the struct it actually is.
|
||||||
|
|
||||||
|
But, that C code will be quite happy if given a class whose first field is a pointer to a C calling
|
||||||
|
convention static member function that calls the next field, the
|
||||||
|
next field being a lambda whose unnameable type is known to the templated object when
|
||||||
|
it was defined, or if it is given a class whose first field is a pointer to a C calling convention
|
||||||
|
static function that does any esoteric C++, or Rust, or Lua thing.
|
||||||
|
|
||||||
# auto and decltype(variable)
|
# auto and decltype(variable)
|
||||||
|
|
||||||
In good c++, a tremendous amount of code behavior is specified by type
|
In good c++, a tremendous amount of code behavior is specified by type
|
||||||
@ -490,7 +609,9 @@ decltype for a type, use it.
|
|||||||
|
|
||||||
In good event oriented code, events are not triggered procedurally, but
|
In good event oriented code, events are not triggered procedurally, but
|
||||||
by type information or data structures, and they are not handled
|
by type information or data structures, and they are not handled
|
||||||
procedurally, as by defining a lambda, but by defining a derived type.
|
procedurally, as by defining a lambda, but by defining a derived type
|
||||||
|
in the sense that you use the virtual method table as the despatch table
|
||||||
|
for handling different events.
|
||||||
|
|
||||||
# Variable length Data Structures
|
# Variable length Data Structures
|
||||||
|
|
||||||
|
@ -51,7 +51,8 @@ A participant who can be targeted is likely to introduce unobvious security
|
|||||||
flaws into the software architecture. All contributors should make some
|
flaws into the software architecture. All contributors should make some
|
||||||
effort to protect themselves against a third party subsequently coercing
|
effort to protect themselves against a third party subsequently coercing
|
||||||
them to use the reputation that they have obtained by contributing to make
|
them to use the reputation that they have obtained by contributing to make
|
||||||
subsequent harmful contributions.
|
subsequent harmful contributions. For example when Telegram founders visited the
|
||||||
|
US, they caught heat.
|
||||||
|
|
||||||
All contributors will use a unique name and avatar for the purpose of
|
All contributors will use a unique name and avatar for the purpose of
|
||||||
contributing to this project, and shall not link it to other names of theirs
|
contributing to this project, and shall not link it to other names of theirs
|
||||||
|
Loading…
Reference in New Issue
Block a user