Salomonsson.se
Jul 26 2016

Haxe Macros, Part 1

When reading up on the Edge-entity system in the post about TLDR I got an idea on how to speed up my Seagal enging a lot using macros. The problem is that macros is a subject with few tutorials and resources online, and it seems a lot of people are afraid to look into it.

I will try to explain what macros are, some good places to start learning more, and a cool example that I’ll expand upon to improve the speed of Seagal!

What is a Macro in Haxe?

Macros in haxe are a really cool concept! It lets you execute haxe-code at compile-time to manipulate the source code itself!

Or to be more clear (this is as far as I have understood anyway):

  1. When compiling your source code Haxe will take your code and turn it into an intermediate language called Abstract Syntax Tree (AST). It really is a huge enum describing your code.

  2. After the AST is generated by the compiler, you can write macros where you can read, write and modify the AST, which means you can modify the source code. Like add variables for example.

  3. Some additional compilation steps, like type checking

  4. A selected haxe backend will read the AST and write out the final source code, for example this could be C++ or javascript.

So as an example, you could read from a variable that does not exist in your class. During compilation you can execute a macro to create that variable. The final code will read your generated variable, and you won’t get a compiler error!

Resources

Here are a few resources I found on the subject. I also got some recommendations from Twitter.

Code

Here is the haxe code (I’m using openFL and is to lazy to rewrite to plain haxe). Notice the @:autobuild metadata, that will execute the build macro on all subclasses of Component.

package;

import haxe.macro.Compiler;
import haxe.macro.Context;
import haxe.macro.Expr.Field;
import openfl.display.Sprite;

class Main extends Sprite {

	public function new() {
		super();
		
		trace("compA: " + new CompA().flag);
		trace("compB: " + new CompB().flag);
	}
}


// ## Component base class
@:autoBuild(MyMacro.BuildComponent())
class Component {
	public function new() {}
}

// ## A sub-class of Component
class CompA extends Component {
	public function new() {
		super();
	}
}

// ## Another sub-class of Component
class CompB extends Component {
	public function new() {
		super();
	}
}

And here is the macro, that will create a public Integer variable which will increase for each class.

package;
import haxe.macro.Context;
import haxe.macro.Expr;

class MyMacro {
	
	private static var count = 0;
	
	macro public static function BuildComponent():Array<Field> {
		var fields = Context.getBuildFields();
		
		var newField = {
			name: "flag",
			doc: null,
			meta: [],
			access: [APublic],
			kind: FVar(macro : Int, macro $v{count++}),
			pos: Context.currentPos()
		};
		
		fields.push(newField);
		
		return fields;	
	}
}

Will output the following text:

Main.hx:13: compA: 1 Main.hx:14: compB: 0

This is really cool, and will help me do some pretty powerful stuff that I’ve for a long time considered “not possible”.

To be continued…