COMMUNITY

Determine type of variable name in expression macro

macro

(Remco Huijser) #1

Working on a vector math experiment where I try to minimize garbage collection (so avoiding creation of new instances) while keeping the math very readable. I am wondering: what is the most convenient way to determine the type of an identity inside a macro.

In the example below, inside the Macro.Vectorize function, can you find out what the compiler thinks will be the type of “position” or “forward”?

class Node
{
    private var position : Vector3;
    private var forward : Vector3;

    private function MoveForward()
    {
       position = Macro.Vectorize(position + forward);
    }
}

(Jens Fischer) #2

Have you tried Context.typeof()?

https://api.haxe.org/haxe/macro/Context.html?#typeof


(Valentin Lemière) #3

And if you want the type of the variable where the macro result will be put there is Context.getExpectedType.

https://api.haxe.org/haxe/macro/Context.html?#getExpectedType


(Remco Huijser) #4

Amazing guys! Thanks for the quick response.
Just wanted to say (again) that the new macro debugging is so awesome :smile:


(Mario Carbajal) #5

I recommend you have a look at inline constructors and operator overloading with abtract classes, with them you can implement inline vector math without the use of macros (only works in Haxe 4.0.0 though)


(Remco Huijser) #6

Thanks for taking the effort to respond @basro.
The thing is that this approach did not fully give me what I wanted last time I tried. The main reason for this is that an abstract class needs to “inherit” from another type. What I want here is avoiding the creation of a new instance. So in fact when doing vector math I would like a Vector3 to be 3 individual float and not 3 float wrapped inside an object.

In the example above I want the code to compile to:

private function MoveForward()
{
    position.x = position.x + forward.x;
    position.y = position.y + forward.y;
    position.z = position.z + forward.z;
}

The value of what I am trying to achieve is not really shown very well in this sample. Especially when you need to perform dot and cross products inside a function (that involves temporary vectors) this would avoid the construction of new instances.

Hope this makes sense. If I have missed anything please let me know!


(Mario Carbajal) #7

@remcohuijser, yes I had understand, that’s what the inline constructors part would achieve. I’ve been using this combo in my own projects to achieve ergonomic and efficient vector math that doesn’t create new instances (unless needed).

I’ve made a small example here: http://try-haxe.mrcdk.com/#35725
Make sure to set the compiler in the options tab to use Haxe 4, check the output to see that there’s no new instances being allocated.


(Mario Carbajal) #8

Regarding assignment of position in your example, in my code I’ve solved that by implementing a set method.
I’m not sure if it’s possible to override the = as an operator using abstracts but it’s probably a bad idea for the cognitive load it would produce.

Here’s an example: http://try-haxe.mrcdk.com/#fb3B1
In it MoveForward compiles into:

Test.MoveForward = function() {
	var this1 = Test.position;
	var v0 = Test.position;
	var v1 = Test.forward;
	this1.x = v0.x + v1.x;
	this1.y = v0.y + v1.y;
};

I believe the compiler optimizer has improved since preview1 and the output would be smaller if you use the latest haxe 4 compiler. Edit: This was wrong.


(Simon Krajewski) #9

This should still produce the same code. It’s hard to gauge if a temporary variable is better or worse than two field access operations.

By the way, that try-haxe allows you to pick a recent development version, too. I don’t know why of all our preview releases it only shows preview.1 though.


(Remco Huijser) #10

Thanks for the explanation @basro!

I knew try-haxe was there but I am not using it often enough: I usually create a new spike project for these kinds of things. Very nice approach to quickly try something.

The result you showed looks very promising! I was wondering why I got such a different result last time. The main difference is that instead of making Vec2Class a class I used a typedef.

I tried that approach here: http://try-haxe.mrcdk.com/#09D74
The result is completely different for the code below:

var a = Vec2.New(1,1);
var b = Vec2.New(1,2);
trace( (a + b).length() );

Haxe 3.4.4 with typedef

var this1 = { x : 1 + 1, y : 1 + 2};
console.log(Math.sqrt(this1.x * this1.x + this1.y * this1.y));

Haxe 3.4.4 with class

var a = new Vec2Class(1,1);
var b = new Vec2Class(1,2);
var this1 = new Vec2Class(a.x + b.x,a.y + b.y);
console.log(Math.sqrt(this1.x * this1.x + this1.y * this1.y));

Haxe 4.0.0 p1 with typedef

var a = { x : 1, y : 1};
var b = { x : 1, y : 2};
var this_x = a.x + b.x;
var this_y = a.y + b.y;
console.log(“Test.hx:33:”,Math.sqrt(this_x * this_x + this_y * this_y));

Haxe 4.0.0 p1 with class

var x = 1 + 1;
var y = 1 + 2;
console.log("Test.hx:37:",Math.sqrt(x * x + y * y));

To me the last one looks most promising: no object creation and very short code. @Simn can you explain the different for Haxe 4.0.0 (I will not use 3.4.4) and is this something you would expect to happen?


(Remco Huijser) #11

@basro I see you are using @:extern on the New function. Why is this?


(Jens Fischer) #12

@:extern can be used to force inlining of an inline function. There’s some situations where the compiler otherwise cancels inlining.


(Remco Huijser) #13

@Gama11 makes sense, thanks for the explanation!


(Mark) #14

Maybe nice to know I did a similar thing to create a vector2 class https://lib.haxe.org/p/hx-vector2d/ Which is also an abstract, which is nice because

  • make instances out of points from other libraries.
  • allow operator overloading. example I can do var point3 = point1 + point2 which translates to var point3 = {x: point1.x + point2.x, y: point1.y + point2.y}.
  • If Haxe compiler can optimize, there are not even instances but it translates to just floats.

it allows to be constructed it in several ways:

var pointA:Vector2d = {x: 2.0, y: 1.5};
var pointB:Vector2d = new Vector2d(2.0, 1.5);

Hope this helps


(Mario Carbajal) #15

@remcohuijser, I’m used to adding @:extern to inline methods I know I only want to use as inline because it prevents the compiler from generating the concrete methods in the output. The compiler will remove them through DCE anyway so it’s not important.

I don’t fully understand why this is happening but you can make the typedef version “work” by changing the arguments of New into float literals. (like this http://try-haxe.mrcdk.com/#d2909)
I believe the inlining is being cancelled because x and y are seen as Int first but later seen as Float. I don’t understand why that would be the case, why is it not seeing them as floats all the time. Smells like a small bug somewhere.


(Remco Huijser) #16

@mark.knol inspiring stuff: thanks for sharing! Especially what you did with the self field is pretty cool!

@basro using a float instead of an int indeed gives a different result and indeed smells fishy…


(Simon Krajewski) #17

You’re likely hitting https://github.com/HaxeFoundation/haxe/issues/7976 with that Float/Int problem.