Notxor tiene un blog

Defenestrando la vida

armory3D y haxe

Notxor
2023-02-03

Este artículo también podría llamarse Armory, un gamengine para blender pero resulta que fracasé en el intento de compilarlo en mi máquina. Supongo que seré muy torpe, pero el README donde da las instrucciones tampoco aclara mucho y haciendo lo que dice he sido incapaz de conseguir compilar armorlab. A pesar de no haberlo conseguido (aún), encontré un lenguaje de esos raros y minoritarios de los que me gustan, o me suelen gustar, o me gusta chafardear. Así que este artículo va sobre este lenguaje y cómo configurar nuestro Emacs para darle soporte, con una introducción a mi fracaso inicial con Armory3D.

Compilando armorlab

Nada más comenzar, primer error:

> armorcore/Kinc/make --from armorcore -g opengl --compiler clang --compile
Unknown platform 'linux', please edit Tools/platform.sh
> find . -name "platform.sh"
./armorcore/Kinc/Tools/platform.sh
> emacs -nw ./armorcore/Kinc/Tools/platform.sh

Después de abrir el fichero platform.sh me encontré el siguiente contenido:

if [[ "$OSTYPE" == "linux-gnu"* ]]; then
    MACHINE_TYPE=`uname -m`
    if [[ "$MACHINE_TYPE" == "armv"* ]]; then
        KINC_PLATFORM=linux_arm
    elif [[ "$MACHINE_TYPE" == "aarch64"* ]]; then
        KINC_PLATFORM=linux_arm64
    elif [[ "$MACHINE_TYPE" == "x86_64"* ]]; then
        KINC_PLATFORM=linux_x64
    else
        echo "Unknown Linux machine '$MACHINE_TYPE', please edit Tools/platform.sh"
        exit 1
    fi
elif [[ "$OSTYPE" == "darwin"* ]]; then
        KINC_PLATFORM=macos
elif [[ "$OSTYPE" == "FreeBSD"* ]]; then
        KINC_PLATFORM=freebsd_x64
elif [[ "$OSTYPE" == "msys"* || "$OSTYPE" == "cygwin"* ]]; then
        KINC_PLATFORM=windows_x64
        KINC_EXE_SUFFIX=.exe
else
        echo "Unknown platform '$OSTYPE', please edit Tools/platform.sh"
        exit 1
fi

Como veis en el error, el $OSTYPE que devuelve mi sistema es linux y el que espera el chismático es linux-gnu. Lo que hice fue, puesto que sabía que el que necesitaba era linux_x64, añadir otro elif como el del siguiente código:

elif [[ "$OSTYPE" == "linux"* ]]; then
  KINC_PLATFORM=linux_x64

Después empecé a ver varios errores que pedían librerías que estaban instaladas y no entendía muy bien qué estaba pasando. Luego me di cuenta, cuando profundicé un poquito en haxe, que me faltaban algunas librerías del lenguaje y no de c++, como pensé al principio. Por tanto, con haxelib instalé las librerías del lenguaje hxcpp y zip.

> haxelib install hxcpp & haxelib install zip

Esto me hace superar esos primeros errores que aparecieron. Sin embargo, llega un momento en que intenta compilar para Wayland y ahí me quedé atascado, porque no lo tengo instalado y no lo instalaré hasta que KDE no tenga un soporte decente de él.

Así pues, Armory descartado por el momento.

Un poco de mi historia con haxe

Aunque lo conocí hace tiempo nunca le hice mucho caso. Me pareció una buena idea pero nunca pasé de leer sobre el lenguaje, mirar por encima algún tutorial y ya.

La primera vez que me lo encontré fue en aquella época en que las que teníamos las webs infladas con el infausto flash. Una época en la que la interactividad y los juegos on line venían enlatados en binarios generados con dicha herramienta. No había alternativa libre hasta que apareció OpenFL que era una implementación libre que sigue evolucionando. Cuando lo miré en aquella época, tampoco era totalmente funcional para suplantar flash, aunque ya se podían hacer cosas con ello. El lenguaje era haxe y lo estuve chafardeando, pero mi reticencia a meter cosas binarias en las páginas web hicieron que lo dejara aparte, como una curiosidad nada más.

Un tiempo más tarde me encontré con un libro del amigo Morci1 que hablaba de la creación de juegos con OpenFL. Si alguien está interesado el libro está disponible en openlibra.com. Recuerdo que me conseguí una copia del libro y lo estuve mirando. Lo ojeé por encima y lo aparqué. No llegué a instalar ni OpenFL ni haxe, pero volvía a tener referencia sobre ellos.

Hace poco, hablando con un allegado, programador, me estuvo contando que la empresa donde trabaja, que hasta ahora lo estaba haciendo principalmente con Java, se está moviendo hacia haxe y están pasando partes de los sistemas que hacen hacia este lenguaje.

Por último, estos días encontré Armony3D y realmente echo de menos hacer jueguecillos con Blender3D desde que eliminaron el gamengine. Cotilleando por la web del motor me decidí a probar y me puse a instalar todo. No he conseguido hacerlo funcionar del todo, como he contado antes, pero bueno, estos son las cosas que hice:

  1. Instalé Blender3D 3.3 LTS, que es la versión que pide Armory3D.
  2. Instalé haxe.
  3. Descargué los fuentes de armorlab y hasta aquí llegué.

No he conseguido hacer funcionar el chismático para lo que quería, pero ya que tengo instalado el lenguaje, vamos a probarlo.

haxe

El lenguaje haxe se define a sí mismo como un lenguaje de propósito general, multiparadigma que soporta tanto POO como programación funcional. Aunque mirando la documentación lo primero que presentan y lo que parece más evolucionado es el apartado POO. La sintaxis es familiar para programadores de c++, java, PHP, c#, etc.

El compilador de haxe puede generar diversas salidas para diversos sistemas y lenguajes.

Plataforma Salida Tipos estáticos
JavaScript fuentes No
HashLink2 byte code + fuentes
Eval Intérprete No
JVM byte code
Java fuentes
PHP7 fuentes No
C++ fuentes
Lua fuentes No
C# fuentes
Python fuentes No
Flash byte code
Neko byte code No
ActionScript fuentes No
PHP5 fuentes No

Para ver cómo funcionaba me propuse hacer un ejemplo sencillo que fuera fácil de entender pero un poco más complejo que el clásico Hola mundo. Ahí va el código:

// Clases de Puntos

class Punto {
    var x:Int;
    var y:Int;

    public function new(x, y) {
        this.x = x;
        this.y = y;
    }

    public function toString() {
        return "Punto(" + x + ", " + y + ")";
    }
}

class Punto3D extends Punto {
    var z:Int;

    public function new(x, y, z) {
        super(x, y);
        this.z = z;
    }

    public override function toString() {
        return "Punto3D(" + x + ", " + y + ", " + z + ")";
    }
}

class PruebaPunto {
    static public function main():Void {
        var p = new Punto(-1, 65);
        var p3 = new Punto3D(21, 32, -6);

        trace(p.toString());
        trace(p3.toString());
    }
}

Configuración de Emacs

En toda la documentación, tanto para Armony3D como para haxe me encontré los pasos para instalar los plugins necesarios para los más variopintos editores. Pero en ningún lado hay referencia de cómo hacerlo para Emacs. Ya que no lo hacen ellos, lo haré yo, porque efectivamente hay paquetes que soportan este lenguaje y también para babel de org-mode (por si alguien se plantea hacer programación literaria con este lenguaje).

Añadí a mi init.el el siguiente código:

(use-package haxe-mode
  :mode ("\\.hx\\'" . haxe-mode)
  :no-require t
  :init
  (require 'js)
  (define-derived-mode haxe-mode js-mode "Haxe"
    "Haxe syntax highlighting mode. This is simply using js-mode for now."))

(use-package battle-haxe
  :hook (haxe-mode . battle-haxe-mode)
  :bind (("S-<f4>" . #'pop-global-mark) ; Para regresar después de visitar una definición
         :map battle-haxe-mode-map
         ("<f4>" . #'battle-haxe-goto-definition)
         ("C-c <f4>" . #'battle-haxe-helm-find-references))
  :custom
  (battle-haxe-yasnippet-completion-expansion t "Mantén esto si quieres expansión de completado yasnippet")
  (battle-haxe-immediate-completion nil "Activar/desactivar el completado inmediato al pulsar «.» u otros prefijos significativos"))
(use-package ob-haxe
  :defer t)

Como veis instalo tres paquetes:

  • haxe-mode proporciona coloreado de sintaxis y pocas cosas más.
  • battle-haxe proporciona autocompletado a través de company y control de sintaxis a través de flycheck.
  • ob-haxe: paquete para org-babel.

Si se va a utilizar en bloques de código dentro de ficheros org hay que darlo de alta también en la lista de lenguajes org-babel-load~languages.

En principio todo funciona como se espera con algunas dificultades. Los paquetes para Emacs soportan la versión 4.0.0 de haxe, mientras que el lenguaje va ya por la versión 4.2.5 y hay algunas cosas que no van finas. Algunas palabras clave (como override) no las colorea como el resto de la sintaxis. El autocompletado lanza errores y a veces no funciona.

Diferentes compilados

Del ejemplo mostrado en el apartado anterior hice algunas pruebas de compilado a diferentes targets. El primero fue correr el scritp a ver si funcionaba todo:

haxe --main PruebaPunto --interp
PruebaPunto.hx:35: Punto(-1, 65)
PruebaPunto.hx:36: Punto3D(21, 32, -6)
  • Salida a Python: Una vez comprobado que todo funcionaba hice la prueba de compilar a Python y ejecutarlo a ver qué nos decía:

    > haxe --main PruebaPunto --python PruebaPunto.py
    > python3 PruebaPunto.py
    Punto(-1, 65)
    Punto3D(21, 32, -6)
    

    El primer paso lo que hace es compilar el código haxe en código Python. Al ejecutar el código generado, la salida es muy parecida a la que produce el original. Por si alguien tiene curiosidad aquí va el código generado:

    import sys
    
    class Punto:
        __slots__ = ("x", "y")
        def __init__(self,x,y):
            self.x = x
            self.y = y
        def toString(self):
            return (((("Punto(" + str(self.x)) + ", ") + str(self.y)) + ")")
    
    class Punto3D(Punto):
        __slots__ = ("z",)
        def __init__(self,x,y,z):
            self.z = None
            super().__init__(x,y)
            self.z = z
        def toString(self):
            return (((((("Punto3D(" + str(self.x)) + ", ") + str(self.y)) + ", ") + str(self.z)) + ")")
    
    class PruebaPunto:
        __slots__ = ()
        @staticmethod
        def main():
            p = Punto(-1,65)
            p3 = Punto3D(21,32,-6)
            print(str(p.toString()))
            print(str(p3.toString()))
    
    class haxe_iterators_ArrayIterator:
        __slots__ = ("array", "current")
        def __init__(self,array):
            self.current = 0
            self.array = array
        def hasNext(self):
            return (self.current < len(self.array))
        def next(self):
            def _hx_local_3():
                def _hx_local_2():
                    _hx_local_0 = self
                    _hx_local_1 = _hx_local_0.current
                    _hx_local_0.current = (_hx_local_1 + 1)
                    return _hx_local_1
                return python_internal_ArrayImpl._get(self.array, _hx_local_2())
            return _hx_local_3()
    
    class python_internal_ArrayImpl:
        __slots__ = ()
        @staticmethod
        def _get(x,idx):
            if ((idx > -1) and ((idx < len(x)))):
                return x[idx]
            else:
                return None
    
    class HxOverrides:
        __slots__ = ()
        @staticmethod
        def stringOrNull(s):
            if (s is None):
                return "null"
            else:
                return s
    
    class python_internal_MethodClosure:
        __slots__ = ("obj", "func")
        def __init__(self,obj,func):
            self.obj = obj
            self.func = func
        def __call__(self,*args):
            return self.func(self.obj,*args)
    
    PruebaPunto.main()
    

    El único cambio que he hecho en el código generado es eliminar algunas líneas en blanco para que no fuera demasiado largo el listado de código dentro del artículo. Todo lo demás es tal cual lo exporta haxe. Vemos que el código ha crecido un poco en complejidad y añade clases por parte de haxe que no hubiéramos añadido nosotros en caso de haber querido hacer el código en Python directamente.

  • También probé con JavaScript. El código generado fue el siguiente:

    (function ($global) { "use strict";
    function $extend(from, fields) {
            var proto = Object.create(from);
            for (var name in fields) proto[name] = fields[name];
            if( fields.toString !== Object.prototype.toString ) proto.toString = fields.toString;
            return proto;
    }
    var Punto = function(x,y) {
            this.x = x;
            this.y = y;
    };
    Punto.prototype = {
            toString: function() {
                    return "Punto(" + this.x + ", " + this.y + ")";
            }
    };
    var Punto3D = function(x,y,z) {
            Punto.call(this,x,y);
            this.z = z;
    };
    Punto3D.__super__ = Punto;
    Punto3D.prototype = $extend(Punto.prototype,{
            toString: function() {
                    return "Punto3D(" + this.x + ", " + this.y + ", " + this.z + ")";
            }
    });
    var PruebaPunto = function() { };
    PruebaPunto.main = function() {
            var p = new Punto(-1,65);
            var p3 = new Punto3D(21,32,-6);
            console.log("PruebaPunto.hx:35:",p.toString());
            console.log("PruebaPunto.hx:36:",p3.toString());
    };
    var haxe_iterators_ArrayIterator = function(array) {
            this.current = 0;
            this.array = array;
    };
    haxe_iterators_ArrayIterator.prototype = {
            hasNext: function() {
                    return this.current < this.array.length;
            }
            ,next: function() {
                    return this.array[this.current++];
            }
    };
    PruebaPunto.main();
    })({});
    

    La forma de conseguir ese código es bastante parecido al de conseguir el de Python:

    haxe --main PruebaPunto --js PruebaPunto.js
    

    Con los lenguajes interpretados no ha habido problema y aunque haxe produce una capa de abstracción por encima, el código generado es bastante asequible y se sigue entendiendo sin mucho esfuerzo.

  • Para compilar a Java encontré dos modos, uno genera únicamente un módulo jar ejecutable:

    > haxe --main PruebaPunto --jvm PruebaPunto.jar
    > java -jar PruebaPunto.jar
    PruebaPunto.hx:35: Punto(-1, 65)
    PruebaPunto.hx:36: Punto3D(21, 32, -6)
    

    Ese comando, con la salida a --jvm sólo genera un archivo comprimido con nombre PruebaPunto.jar que se puede ejecutar directamente. Sin embargo, si queremos también el código fuente la salida debemos hacerla a --java~:

    Dado mi desconocimiento del sistema me sorprendí cuando al ejecutar

    haxe --main PruebaPunto --java PruebaPunto.java
    haxelib run hxjava hxjava_build.txt --haxe-version 4205 --feature-level 1 --out PruebaPunto.java/PruebaPunto
    javac "-sourcepath" "src" "-d" "obj" "-g:none" "@cmd"
    

    Vi que hacía más cosas y cuando intenté abrir el .java resultó ser un directorio con un gran número de archivos dentro, un directorio src con el código fuente. También encontramos un directorio obj donde están los archivos .class compilados. También está el .jar generado (al ejecutarlo se obtiene la misma salida que antes). Los ficheros .java generados son bastante complejos. El fichero fuente de la clase Punto tiene más de 300 líneas.

    Para hacernos una idea, los archivos fuente que se generan son:

    > tree src
    src
    └── haxe
        ├── Exception.java
        ├── exceptions
        │   ├── NotImplementedException.java
        │   └── PosException.java
        ├── iterators
        │   ├── ArrayIterator.java
        │   └── ArrayKeyValueIterator.java
        ├── java
        │   └── Init.java
        ├── lang
        │   ├── Closure.java
        │   ├── DynamicObject.java
        │   ├── EmptyObject.java
        │   ├── Enum.java
        │   ├── FieldLookup.java
        │   ├── Function.java
        │   ├── HxObject.java
        │   ├── IEquatable.java
        │   ├── IHxObject.java
        │   ├── ParamEnum.java
        │   ├── Runtime.java
        │   ├── StringExt.java
        │   ├── StringRefl.java
        │   ├── VarArgsBase.java
        │   └── VarArgsFunction.java
        ├── Log_Anon_62__Fun.java
        ├── Log.java
        ├── root
        │   ├── Array.java
        │   ├── PruebaPunto.java
        │   ├── Punto3D.java
        │   ├── Punto.java
        │   ├── Reflect.java
        │   ├── Std.java
        │   ├── StringBuf.java
        │   └── Type.java
        └── ValueException.java
    
  • Y, por último, también hice las consiguientes pruebas con clang. También la salida del comando es más compleja que en el caso de los lenguajes interpretados.

    > haxe --main PruebaPunto --cpp PruebaPunto.cc
    ...
    Creating /home/notxor/proyectos/haxe-intro/PruebaPunto.cc/obj/linux64/__pch/haxe/hxcpp.h.gch...
    ...
    Compiling group: runtime
    g++ -D_CRT_SECURE_NO_DEPRECATE -DHX_UNDEFINE_H -c -fvisibility=hidden -O2 -fpic -fPIC -Wno-overflow -DHX_LINUX -DHXCPP_M64 -DHXCPP_VISIT_ALLOCS(haxe) -DHX_SMART_STRINGS(haxe) -DHXCPP_API_LEVEL=400(haxe) -m64 -DHXCPP_M64 -I/home/notxor/opt/haxe/lib/hxcpp/4,2,1/include ... tags=[haxe]
    ...
    Link: PruebaPunto
    > PruebaPunto.cc/PruebaPunto 
    PruebaPunto.hx:35: Punto(-1, 65)
    PruebaPunto.hx:36: Punto3D(21, 32, -6)
    

    Efectivamente genera el ejecutable, pero cargado con toda una capa de abstracción. Aunque no con tanta complejidad como en el caso de Java. En este caso genera un par de directorios: include y src donde se encuentran los ficheros de cabecera y los fuentes, respectivamente, de nuestras clases.

    > tree include
    include
    ├── haxe
    │   └── Log.h
    ├── PruebaPunto.h
    ├── Punto3D.h
    ├── Punto.h
    └── Std.h
    
    > tree src
    src
    ├── __boot__.cpp
    ├── __files__.cpp
    ├── haxe
    │   └── Log.cpp
    ├── __lib__.cpp
    ├── __main__.cpp
    ├── PruebaPunto.cpp
    ├── Punto3D.cpp
    ├── Punto.cpp
    ├── __resources__.cpp
    └── Std.cpp
    

    Aunque la estructura es simple, el contenido de esas clases se aleja de la sencillez de nuestro código y, por ejemplo, la clase Punto tiene una cabecera y un cuerpo de más de 100 líneas cada archivo.

Conclusiones

haxe es una herramienta que parece madura para producir aplicaciones multiplataforma. Parece que tiene una comunidad activa alrededor principalmente de la programación de juegos. Sin embargo, la última referencia que tenía sobre este lenguaje fue de una empresa que estaba migrando bastante del sistema que tenía en Java a este lenguaje.

Por cuatro pruebas que he hecho, aún no le he visto las ventajas que puede tener con hacer directamente el código deseado, más allá de lo que pueden ser los juegos. Aunque supongo, que vistas la librerías de soporte a JavaScript, a PHP... salidas a ejecutables compilados para la máquina, o compilados para la jvm... En fin, capacidad para gestionar la aplicación y su interface con el mismo lenguaje.

De momento, si no lo necesito, no creo que lo use mucho por la complejidad de todo el sistema, pero lo mismo a alguno de los que puedan leer este artículo les es útil.

Footnotes:

1

Morci era el nick que utilizaba cierto profesor de la Universidad de Castilla la Mancha por los foros de BlenderAdictos y con el que coincidí en alguna ocasión, compartiendo sala de conferencias o alguna publicación. No creo que lea esto, pero si lo hace le envío un saludo.

2

Máquina virtual propia de haxe.

Categoría: haxe emacs

Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en esta cuenta de Mastodon, también en esta otra cuenta de Mastodon y en Diaspora con el nick de Notxor.

Si usas habitualmente XMPP (si no, te recomiendo que lo hagas), puedes encontrar también un pequeño grupo en el siguiente enlace: notxor-tiene-un-blog@salas.suchat.org

Disculpen las molestias.