armory3D y haxe
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:
- Instalé Blender3D 3.3 LTS, que es la versión que pide Armory3D.
- Instalé
haxe
. - 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 | Sí |
Eval | Intérprete | No |
JVM | byte code | Sí |
Java | fuentes | Sí |
PHP7 | fuentes | No |
C++ | fuentes | Sí |
Lua | fuentes | No |
C# | fuentes | Sí |
Python | fuentes | No |
Flash | byte code | Sí |
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 decompany
y control de sintaxis a través deflycheck
.ob-haxe
: paquete paraorg-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 dehaxe
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 nombrePruebaPunto.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 directoriosrc
con el código fuente. También encontramos un directorioobj
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 clasePunto
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
ysrc
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:
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.
Máquina virtual propia de haxe
.
Comentarios