On Friday, 11 April 2014 at 14:49:49 UTC, Dicebot wrote:
On Friday, 11 April 2014 at 14:05:17 UTC, Vlad Levenfeld wrote:
I'm trying to cut down on some boilerplate with this function:
const string link (alias variable) ()
{
const string name = __traits (identifier, variable);
return name~`= glGetUniformLocation (program, "`~name~`");`;
}
Applied in this kind of context:
this ()
{
super ("default.vert", "gradient.frag");
mixin link!start_color;
mixin link!final_color;
mixin link!center_pos;
mixin link!lerp_vec;
mixin link!lerp_range;
}
And the first mixin in every such function works fine, but the
rest don't. Specifically, I get this error:
source/main.d(483): Error: mixin link!final_color link isn't a
template
This syntax is only supported by template mixins and you
generate actual code as string. Template mixins can't be used
inside functions and intended only for injecting declarations.
for each mixin after the first. If I put the argument to mixin
in parenthesis:
mixin (link!start_color);
I get:
source/main.d(483): Error: value of 'this' is not known at
compile time
This is unfortunate flaw of how class members get passed as an
alias parameters. compiler tries to pass it together with
context pointer (this) which can't happen because there is no
valid context pointer at code generation point.
Finally, removing the parenthesis and putting each mixin
statement in its own block:
{mixin link!start_color;}
compiles, but the mixed-in string has no apparent effect.
This does not mixin generated code but function itself, so it
has no effect as expected.
To workaround context pointer limitation you can use tupleof +
index trick:
const string link (T, size_t index) ()
if (is(T == class) || is(T == struct))
{
const string name = __traits (identifier, T.tupleof[index]);
return name ~ `= 42;`;
}
class X
{
int a, b;
this ()
{
mixin (link!(X, 0));
mixin (link!(X, 1));
}
}
void main()
{
auto x = new X();
import std.stdio;
writeln(x.a, " ", x.b); // 42 42
}
This is a cool trick. It made me wonder if I could somehow
automate the entire uniform linking process, and I wound up with
the following solution:
/* checks for the presence of an attribute for a symbol belonging
to T */
template has_attribute (T, string symbol, string attribute)
{
const bool has_attribute ()
{
mixin (
`foreach (label; __traits (getAttributes, T.`~symbol~`))
static if (label == attribute)
return true;
`);
return false;
}
}
/* returns a tuple or an array or something?? of all fields of T
which have a given attribute */
template collect_fields (T, string attribute)
{
const string[] collect_fields ()
{
const string[] collect_fields (T, string attribute,
members...)()
{
static if (members.length == 0)
return [];
else static if (members[0] == "this")
return [];
else static if (has_attribute!(T, members[0], attribute))
return collect_fields!(T, attribute, members[1..$]) ~
members[0];
else return collect_fields!(T, attribute, members[1..$]);
}
return collect_fields!(T, attribute, __traits (allMembers,
T));
}
}
/* returns a mixin-able string to get all uniform locations for a
shader, provided that the variable names are identical on both
client and server */
const string link_uniforms (T_shader) ()
{
string command;
foreach (uniform; collect_fields!(T_shader, "uniform"))
command ~= uniform~` = glGetUniformLocation (program,
"`~uniform~`"); `;
return command;
}
And now all of my Shader subclass constructors look like this:
this ()
{
super ("default.vert", "default.frag");
mixin (link_uniforms!(typeof (this)));
}
Which works like a charm as long as my uniform handles look like
this:
@("uniform") GLint start_color;
@("uniform") GLint start_color;
... etc
Which also strikes me as a really neat way to visually separate
the client-side shader variables from the uniform handles.
And I am now officially 100% sold on D, haha. Who would have
thought metaprogramming could be so straightforward...
Corrections and suggestions welcome!