Tile and DisplayObject common position interface

In my project using ECS, currently I have to use two systems, like so (simplified):

var movingShapes:Array<{ displayObject:openfl.display.DisplayObject, position:Position }>;
var movingTiles:Array<{ tile:openfl.display.Tile, position:Position }>;

for(component in movingShapes) {
    component.displayObject.x = component.position.x;
    component.displayObject.y = component.position.y;
}

for(component in movingTiles) {
    component.tile.x = component.position.x;
    component.tile.y = component.position.y;
}

I’d like to use single system to deal with both of those, unfortunately they don’t share an interface. So I tried using typedef as @PSvils told me on IRC:

typedef Movable = {
    var x:Float;
    var y:Float;
};

var moving:Array<{ item:Movable, position:Position }>;

//adding some items here

for(component in movingShapes) {
    component.item.x = component.position.x;
    component.item.y = component.position.y;
}

Works fine for DisplayObjects, however it throws Cannot create property x on openfl.display.Tile. I peeked into Tile source code, and it seems x and y don’t fit into physical field criteria.

Can I do something more about it, or am I stuck with double implementation?

1 Like

You could use Reflect.getProperty(), which falls back to field access if there isn’t a property.

If you don’t like that option or if it’s too slow, consider making a wrapper:

abstract Movable(Either<DisplayObject, Tile>) from Either<DisplayObject, Tile> {
    @:from private static inline function fromDisplayObject(d:DisplayObject):Movable {
        return Left(d);
    }
    
    @:from private static inline function fromTile(t:Tile):Movable {
        return Right(t);
    }
    
    public var x(get, set):Float;
    public var y(get, set):Float;
    
    private function get_x():Float {
        switch(this) {
            case Left(d):
                return d.x;
            case Right(t):
                return t.x;
        }
    }
    private function set_x(value:Float):Float {
        switch(this) {
            case Left(d):
                return d.x = value;
            case Right(t):
                return t.x = value;
        }
    }
    
    private function get_y():Float {
        switch(this) {
            case Left(d):
                return d.y;
            case Right(t):
                return t.y;
        }
    }
    private function set_y(value:Float):Float {
        switch(this) {
            case Left(d):
                return d.y = value;
            case Right(t):
                return t.y = value;
        }
    }
}
1 Like

Never knew about haxe.ds.Either, this would save me many headaches! Thanks

Follow up question:

@:to public inline function toDisplayObject():Null<DisplayObject>
{
    return switch this { case Left(v): return v; case Right(v): return null; };
}

@:to public inline function toTile():Null<Tile>
{
    return switch this { case Right(v): return v; case Left(v): return null; };
}

It seems due to using Null<T>, the casting doesn’t use my methods. I can’t return only this.Left or something like that, so what would be the correct approach?

I just went with throwing an error if trying to cast to the other type (as I know which type appears where).

One pitfall of all this though: using cast(foobar, DisplayObject) will not call #toDisplayObject, it’s only for implicit casts, otherwise you have to call .toDisplayObject directly.

DisplayObject should work instead of Null<DisplayObject>, I think. DisplayObject is nullable

Right. DisplayObject and Tile are classes, so they’re both nullable.

(As far as I know, you only need to use Null<> for Ints, Floats, and enums. There might be other cases, but you definitely don’t need it for classes.)

Another option:

var tile:Tile = movable;
var displayObject:DisplayObject = movable;

Yet another option:

(movable:Tile).id = 3;
trace((movable:DisplayObject).parent != null);

(Obviously the movable variable can’t be both types at once, so one line will fail in the second example. But the basic idea works.)

You don’t need that many returns. You can safely remove the one outside the switch block, or you can remove the two inside.

cool haxe - sugar kung fu, “tile” is :key2: word?
…(*mhm) like point care arithmetics :slight_smile:?

Tile is a class, just like DisplayObject.

Are you talking about how you can cast pointers? Otherwise, I don’t see how pointer arithmetic relates to this.

never :wink: , i thought conways superposition ( missunderstand… shame on me in joking here ; )

Don’t worry about it.