Generate typed container-class with a macro

62 views
Skip to first unread message

Stefan

unread,
Nov 17, 2012, 5:06:24 AM11/17/12
to haxe...@googlegroups.com
Hi everybody,

I have a question about macros. I want to create something like this:

When I call...

componentManager.set( 1, new TestComponent() );
or
var tc = componentManager.get( 1, TestComponent );

1. I want to rewrite the call like this (I got that solved):

componentManager.setTestComponent( 1, new TestComponent() );
var tc = componentManager.getTestComponent( 1 );


2. and additionally add the necessary Array and setter/getter to the ComponentManager-Class:

// declaration
var testComponents : Array<TestComponent>;

// initialization (somewhere)
testComponent = new Array<TestComponent>();

// and getter and setter:
public function setTestComponent( eId : Int, tc : TestComponent ) : TestComponent { // do it }
public function getTestComponent( eId : Int ) : TestComponent { // do it }


I solved the first part with this code in ComponentManager:

@:macro public function set( body:Expr, eId:ExprRequire<Int>, cInst : ExprRequire<Dynamic> ) : Expr
{
    var inst = Context.typeof( cInst );
   
    switch( inst ) {
        case TInst( t, params ):
            var className = t.toString().substring( t.toString().lastIndexOf(".") + 1 );
            return { expr: ECall( {expr : EField( body, "set" + className), pos:body.pos}, [eId,cInst]), pos: body.pos };
        default:
            throw 'We need an instance here.';
    }       
}



But how can I do the second part?
I understand this has to be done with a @:build macro for ComponentManager. But I have no idea how to tell this macro the types it should work on (here TestComponent).

Or do I need to compile two times? Once for the ComponentManager-build-macro, save the file and then compile again for the set/get-macros?

Or do you think this is just a bad idea? :)

Any help is really appreciated!
Thanks,
Stefan

Juraj Kirchheim

unread,
Nov 17, 2012, 5:29:48 AM11/17/12
to haxe...@googlegroups.com
There's no straight forward way to solve this, so I have to ask: what's wrong with the very first approach?

Stefan

unread,
Nov 17, 2012, 5:41:15 AM11/17/12
to haxe...@googlegroups.com
I'm not sure, I understand?! I have no first approach yet... :)

I managed to rewrite the set and get-calls with a macro (part 1.), but that does not do anything without adding the setter/getter to the ComponentManager-Class (as I described in part 2).

Juraj Kirchheim

unread,
Nov 17, 2012, 6:00:14 AM11/17/12
to haxe...@googlegroups.com
I meant this:

componentManager.set( 1, new TestComponent() ); 
var tc = componentManager.get( 1, TestComponent );

That's perfectly valid code. I see nothing wrong with it.

OTOH the solution you describe is impossible to implement, because the build macro will definitely execute before your component registration calls. Note that I am not saying that there's no macro based solution here, but without understanding what you're trying to do and what's wrong with the macro-free version, I cannot tell you what it might look like.

Regards,
Juraj

Stefan

unread,
Nov 17, 2012, 6:50:20 AM11/17/12
to haxe...@googlegroups.com
Ah, I see!

I try to explain shortly (but I can't... :) ).

I want to implement an entity/component system (based losely on the Artemis-Entity-Framework: http://gamadu.com/artemis/ ) for my (hobby) game engine . I have already implemented two working entity/component-systems, but in these systems I have to cast the components when I want to use them and I want to avoid that by the macro.

An entity in the system is a "game-object" (the player, an enemy, whatever) and is represented by an int-value.
Components are properties of the entities (like position, velocity, a bitmap, etc) and are linked to the entities (see below).
The ComponentManager manages the associations between entities and components.

so, basically in my systems I do something like this:

var entityId : Int = componentManager.createEntity();
var position : PositionComponent = new PositionComponent( 0, 0 );
var velocity : VelocityComponent = new VelocityComponent( 1, 0 );

componentManager.set( entityId, position );    // this links the position with the entity
componentManager.set( entityId, velocity );


and so on. That works, because PositionComponent and VelocityComponent implement the interface IComponent and the ComponentManager saves them here:

var components : IntHash<Array<IComponent>>;    

The keys of this IntHash are internally managed typeIds of the components and the indexes (or indices) of the Arrays of IComponents are the entityIds.

All that (and much more) works, BUT when I want to get() a component later in a system I have to do something like this:

// lets say I want the position of entity 2
var posComponent : PositionComponent = cast( componentManager.get( 2, PositionComponent ), PositionComponent );

I want to avoid this need to cast (because it's slow). I don't find any 'architectural' solutions to this (most EntitySystems I have read about have the same 'problem'), so I thought of a macro solution. Basically I want to generate all necessary Arrays and getters/setters for the components in the ComponentManager and then rewrite all set() and get()-calls from my systems to the generated methods.

Thanks for reading!
Stefan

Juraj Kirchheim

unread,
Nov 17, 2012, 7:40:58 AM11/17/12
to haxe...@googlegroups.com
The macro-free way would be to parametrize the signature of the retrieval method like this:

    function get<A>(entityId, c:Class<A>):A

That way you won't have to cast, at least not outside the method.

Regards,
Juraj

Stefan

unread,
Dec 11, 2012, 5:40:40 AM12/11/12
to haxe...@googlegroups.com
Hi again!

I found two ways to solve this. :)

Juraj, thank you for the idea to parameterize the get/set-calls in this way. I didn't know about this and it lead me to a parameterized solution that is used in Ash-Haxe (https://github.com/nadako/Ash-HaXe) that uses an nme.ObjectHash. A little bit modified for my needs it looks like this:

   [...]
    var components          : Array<ObjectHash<Class<Dynamic>, Dynamic>>;

    public function new() {
        components        = new Array<ObjectHash<Class<Dynamic>, Dynamic>>(); // the objectHashes are initialized somewhere else...
    }
       
    public function set<T>( eId : Int, component:T, componentClass:Class<T> = null ) : Void {
        var entityComponents = components[eId];
       
        if (componentClass == null)
            componentClass = Type.getClass(component);

        entityComponents.set(componentClass, component);
    }
       
    public function remove<T>(eId:Int, componentClass:Class<T>):T {
        var component:T = components[eId].get(componentClass);
        if (component != null) {
            components[eId].remove(componentClass);
            return component;
        }
        return null;
    }

    public function get<T>( eId : Int, componentClass:Class<T>):T {
        return components[eId].get( componentClass );
    }
   
Very nice.

Additionally I managed to create the macro-version I described above by using some metadata to tell the build-macro which arrays to generate. It is less flexible than the objecthash method (and probably not an elegant way to do this), but it's 3-4 times faster. :)

So, thanks again!
Bye!
Reply all
Reply to author
Forward
0 new messages