On Thursday, 9 June 2016 at 23:53:33 UTC, BBasile wrote:
On Thursday, 9 June 2016 at 21:02:26 UTC, maik klein wrote:
I am currently writing a task system and I have this case
where I want to send a delegate to a different thread but this
delegate also captures a variable. I use this to implement a
"future".
Now as far as I know this delegate will allocate GC memory and
I just wanted to avoid that, just for fun.
Here is the code https://dpaste.dzfl.pl/cd77fce99a5b
I have only worked on it a couple of hours and I am sure there
are many problems with it.
Basically the idea is that you can use normal lambda syntax.
If you want a function that returns and int and takes an int,
you can write it like this:
(int i) => i
If you want a function that returns an int, takes an int, but
also captures and int you would write it like this
(int i, int captured) => i + captured
But you also have to declare the base function type without
the captured variables beforehand.
Fn!(FnType!(int, int), (int i, int captured) => i +
captured)(42);
That is how I know what the captured variables are.
The only part that is currently missing are polymorphic
delegates. They are not too useful if I can't pack them into
the same array.
I guess I have to do this with classes/interfaces.
Thoughts?
Has this been done before?
What you have here is more functor, to the extent that you can
memorize the parameters. The problem is that it's not
compatible with the delegates as defined in the language.
Actual D delegates get collected when we take the address and
the context of a member function with "&". To allocate the
delegate itself in the non GC heap is easy (with a struct that
contains two pointers).
Your message has motivated me to make this, thanks to a union
true nogc delegates are possible:
=========================================
module runnable;
union Delegate(FT)
{
// delegate layout as defined in the D ABI
struct DgMembers
{
void* ptr;
FT funcptr;
}
DgMembers members;
// the 2nd member is "true" D delegate type
import std.array: replace;
mixin("alias DT = " ~ FT.stringof.replace("function",
"delegate") ~ ";");
DT dg;
}
void main() @nogc
{
static struct Foo
{
@nogc string delegate() test;
@nogc string source() {return "test";}
}
import std.experimental.allocator.mallocator;
import std.experimental.allocator;
Foo foo;
alias DelegateT = Delegate!(typeof(&Foo.source));
// &Foo.source takes the static address, i.e in the data
segment
DelegateT* dg = make!DelegateT(Mallocator.instance);
dg.members.ptr = &foo;
dg.members.funcptr = &Foo.source;
foo.test = dg.dg;
assert(foo.test() == "test");
}
=========================================
Here you have a true D delegate that's conform with the
language.
The problem is I currently do this
struct LocalTaskQueue{
...
Array!(Box!Fiber) work;
Array!(Box!Fiber) queuedWork;
...
}
This is where I submit my delegates to, but before that I also
wrap them inside a fiber. Btw "Box" is my version of Unique.
I execute a fiber inside work and if it is done I want to delete
it, because it is not needed anymore. If it is on hold it
transfer it from "work" to queuedWork.
That means I need be able to put my delegates inside a fiber,
this is the constructor for a Fiber currently.
this( void delegate() dg, size_t sz = PAGESIZE*4 ) nothrow
in
{
assert( dg );
}
body
{
allocStack( sz );
reset( dg );
}
I mean I can easily convert my "Fn" version to delegate with
&foo.opCall, but then I would not know how long my "Fn" should
stay alive.
I think what I can do is switch my struct Fn to a class and
implement my own function polymorphism.
I would then do
Array!(Box!Fiber) work;
Array!(Box!PolymorphicFn!(void, void)) rawFunction;
and when I delete a fiber from work I can delete it from
rawFunction.
But that seems to be so much trouble just to avoid a bit of GC.