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:
reaction.la 2024-07-04 00:18:48 +00:00
parent 4678fba3ce
commit fe3015b851
No known key found for this signature in database
3 changed files with 133 additions and 7 deletions

View File

@ -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

View File

@ -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

View File

@ -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