Este repositorio contiene los archivos del proyecto de Godot Engine correspondiente al Core del HUB.
El HUB es un entorno que permite ejecutar scripts del lenguaje de scripting de Godot, GDScript. Fue desarrollado con la idea de que sea 100% manipulable sin la necesidad de recompilar y por eso el programa resultante de exportar este proyecto no hace nada más que levantar un script para que sea este el que realmente inicialice el entorno. Dicho programa es el Core del HUB.
Al descargar y exportar este proyecto se obtendrá el Core compilado. Al ejecutarlo aparecerá un mensaje de error indicando que no se puede localizar el archivo HUB.gd. Esto es porque el Core en sí no hace nada. Para funcionar necesita los archivos fuente del HUB. Esos archivos, como todos los archivos que maneja el HUB, son scripts de GDScript y, por lo tanto, pueden ser modificados a gusto.
El script principal del HUB es HUB.gd. Sin este archivo, el Core sólo mostrará un error. Este script tampoco hace mucho. Se encarga de crear e inicializar cada uno de los módulos necesarios para poder comenzar a utilizar el HUB. Cuando el HUB está inicializado, funciona como una línea de comandos. En la pantalla se muestra un campo para ingresar texto y un panel de mensajes. Al escribir un comando en el campo de texto y presionar la tecla Enter, se ejecutará el script correspondiente al comando ingresado. Al igual que en una terminal, esto funciona localizando un archivo con el mismo nombre que el comando ingresado (tras agregarle la extensión .gd), sólo que en lugar de buscar y ejecutar archivos binarios, se trabaja con scripts de GDScript. Esto hace que sea fácil agregar nuevos scripts o modificar los existentes.
Como ya se mencionó, el Core por sí solo no hace nada; necesita los archivos externos de GDScript para funcionar. Por una restricción del motor de Godot, estos archivos deben estar ubicados en un directorio específico del sistema operativo. A dicho directorio se lo llamará la ruta_raiz del HUB. Dentro de la ruta_raiz debe haber distintas carpetas que organicen todos los tipos de archivos con los que el HUB puede interactuar. Los archivos fuente ya mencionados (los que definen el comportamiento del HUB) deben estar en la carpeta src, dentro de la ruta_raiz. Estos no deberían ser modificados, a menos que sepan lo que están haciendo.
Todos los archivos del HUB (a menos que se trate de archivos muy específicos, como los archivos multimedia) deben tener la extensión .gd. Sin embargo, existen distintos tipos de archivos con distintas funcionalidades. La mayoría son scripts (es decir, código ejecutable) pero no todos se ejecutan de la misma manera. Una primera restricción para que un archivo .gd sea considerado un script del HUB es que este implemente la función inicializar(hub), que es llamada con el HUB como parámetro cada vez que se adjunta un script a un nodo. El tipo de un archivo se identifica por la carpeta que lo contiene dentro de la ruta_raiz y por el contenido del mismo. La convención es iniciar las dos primeras líneas de cada archivo con el nombre y el tipo del archivo en cuestión, tras doble # y un espacio. Por ejemplo, las dos primeras líneas del archivo HUB.gd son:
## HUB
## SRC
El código SRC indica que el archivo es un script fuente del HUB. Cada tipo tiene un código asociado. En general esto es información para los usuarios y programadores pero ciertas partes del HUB asumen que esas dos líneas existen e incluso utilizan la información allí suministrada, así que se recomienda seguir la convención. Otro tipo común de archivo es el tipo Comando. Los archivos de comandos definen funciones que serán ejecutadas cuando se ingrese su nombre en la terminal del HUB. Para que un archivo sea reconocido como un comando por el HUB, este debe estar ubicado en la carpeta comandos dentro de la ruta_raiz, usar el código Comando en la segunda línea e implementar la función comando(argumentos), la cual será llamada cuando el usuario ingrese el comando correspondiente en la terminal del HUB.
También se pueden crear archivos con lotes de comandos para ejecutar. A diferencia de los dos anteriores, este tipo de archivo no es un script válido de GDScript, ya que cada línea contiene un comando del HUB y por lo tanto, no respeta la sintaxis de GDScript. Para que un archivo sea reconocido como un lote de comandos por el HUB, este debe estar ubicado en la carpeta shell dentro de la ruta_raiz y usar el código SH en la segunda línea. Para poder ejecutar un lote de comandos desde la terminal se necesita el comando sh. Más adelante se verán otros tipos de archivos.
Otra premisa del HUB es proveer al usuario una capa de abstracción orientada a objetos por encima del sistema de nodos de Godot. En esta capa, se manipulan objetos cuyo comportamiento viene dado por los componentes que lo componen y por los scripts que se le adjuntan. En Godot, cada nodo tiene un tipo (una clase) y un script asociado. El conjunto de funciones que puede ejecutar ese nodo es la unión entre las funciones de la clase a la que pertenece y las funciones definidas en el script.
En el HUB, un objeto puede tener varios componentes y varios scripts asociados. En lugar de ejecutar funciones, los objetos responden a mensajes. A cualquier objeto se le puede enviar cualquier mensaje. Para enviarle un mensaje a un objeto se debe usar la función mensaje(metodo, parametros) que todo objeto implementa. A través de la abstracción de mensajes y métodos, un objeto puede responder a un mensaje ejecutando una función definida en alguno de sus scripts adjuntos. Luego, el conjunto de funciones que puede ejecutar un objeto del HUB es la unión entre las funciones definidas en cada uno de los scripts que el objeto posee.
Nota: A diferencia del paradigma orientado a objetos, en el HUB no todo es un objeto. Muchos nodos (por ejemplo, los módulos del HUB) no respetan la interfaz de objeto y los objetos están compuestos por cosas que no necesariamente son objetos. Se consideran objetos del HUB únicamente los nodos que tienen adjunto el script objeto y fueron creados utilizando el módulo de objetos del HUB.
El principal objetivo de esta abstracción es desarrollar scripts de comportamiento lo más minimales posible para que sea fácil modificar los existentes y crear nuevos. Esto no puede lograrse en el entorno de Godot ya que para definir el comportamiento de un nodo, toda la funcionalidad deseada debe estar implementada en un mismo script. Al igual que en el caso de los nodos de Godot, los objetos se organizan en una jerarquía tipo árbol. Al iniciar el HUB existe un único objeto (raíz de la jerarquía de objetos) llamado Mundo.
Para crear un nuevo objeto se cuenta con la función del módulo de objetos del HUB crear(hijo_de=HUB.nodo_usuario.mundo). Por defecto, el nuevo objeto se crea vacío y como hijo directo del Mundo en la jerarquía. Un objeto vacío puede responder a cualquier mensaje, aunque como en el paradigma orientado a objetos, a la mayoría de los mensajes responderá que no entiende el mensaje recibido (MessageNotUnderstood).
Para que el objeto empiece a responder mensajes adecuadamente se le deben adjuntar scripts de comportamiento con la función agregar_comportamiento(nombre_script). Los scripts de comportamiento son otro tipo de archivos del HUB sin ninguna otra particularidad que la de definir funciones. Un objeto sabrá responder un mensaje si alguno de sus scripts de comportamiento asociados implementa la función correspondiente. Para que un archivo sea reconocido como un script de comportamiento por el HUB, este debe estar ubicado en la carpeta comportamiento dentro de la ruta_raiz y usar el código Comportamiento en la segunda línea.
Hasta ahora los comandos fueron sólo funciones que se ejecutan ininterrumpidamente de principio a fín. Pero el HUB permite también definir programas que se ejecuten como procesos en segundo plano. Esto no quiere decir que los procesos se ejecuten en paralelo. De hecho, tanto los comandos como los procesos se implementan como scripts adjuntos a nodos. La diferencia es que en el caso de los comandos, existe un único nodo para cada comando y cada vez que se quiere ejecutar dicho comando se llama a la función comando(argumentos) de ese nodo. En el caso de los programas, cada vez que se crea un proceso, se crea un nuevo nodo al que se le adjunta el script correspondiente, por lo que podrían tenerse varias instancias de un mismo programa ejecutándose a la vez (nuevamente, esto no quiere decir que ejecuten en paralelo sino que ambos nodos están vivos a la vez e incluso pueden interactuar entre sí).
Para crear un proceso se debe llamar a la función nuevo(programa, argumentos) del módulo de procesos del HUB. El parámetro programa debe ser un script de programa válido. Para que un archivo sea reconocido como un script de programa por el HUB, este debe estar ubicado en la carpeta programas dentro de la ruta_raiz, implementar la función finalizar() y usar el código Programa en la segunda línea. Además, los scripts de programas difieren al resto de los scripts en cuanto a que la función inicializar además de tomar como parámetro el HUB, debe tomar como parámetro el identificador de proceso y los argumentos. Notar que los procesos siguen vivos por defecto (incluso tras terminar de ejecutar la función de inicialización) y no finalizan hasta que llamen explícitamente a la función finalizar(pid) del módulo de procesos del HUB (o hasta que el proceso sea terminado desde afuera). La función finalizar(pid) del módulo de procesos a su vez llama a la función finalizar del programa, así que no es necesario que un programa llame a su función de finalización si está a punto de finalizar. Sólo debe llamar a la función finalizar(pid) del módulo de procesos.
Otra diferencia importante entre comandos y programas es que los programas pueden, a su vez, definir comandos. Esto significa que, tras lanzar un proceso, los comandos lanzados en la terminal pueden interpretarse tanto como comandos globales (los descriptos hasta ahora) o como comandos dentro del programa. Para definir un comando "X" en un programa sólo hay que declarar una función "__X(argumentos)" en el script del programa. Cuando el proceso esté en ejecución, al lanzar el comando "X" en la terminal, en lugar de ejecutar el script X.gd se ejecutará la función "__X" correspondiente en el script de dicho proceso (si es que esta está definida). Si el proceso en ejecución no tiene definido el comando ingresado, entonces se ejecuta el comando global. Para forzar al HUB a ejecutar el comando global aunque el proceso actual tenga definido un comando con el mismo nombre, se le debe anteponer el caracter "!".
Cada script que se ejecuta en el HUB se ejecuta dentro de un entorno. Un entorno se define con un identificador de proceso (pid), de tipo string y una secuencia de comandos. El proceso por defecto es el HUB, un proceso se crea desde el inicio y que no se puede finalizar y cuyo identificador es "HUB". Cuando no hay ningún proceso en ejecución, este es el proceso actual. Los comandos ejecutados por el usuario desde la terminal del HUB se ejecutan en el entorno correspondiente al proceso por defecto, a menos que el comando ingresado no sea un comando global sino un comando correspondiente al proceso actual. Luego de ejecutar un comando, al entorno se le agrega el comando ejecutado. Si este comando lanza otros comandos, cada uno de estos se va agregando al entorno de ejecución actual. Si se activa la opción correspondiente de la terminal, cada mensaje enviado muestra primero el entorno en el que se envió dicho mensaje. Como el "HUB" es el proceso por defecto, cuando el entorno incluye a este proceso sólo se muestra la secuencia de comandos.
Como en cualquier lenguaje de programación, hay estructuras o funciones que son requeridas por muchos scripts y no tendría sentido definirlas en cada uno. Para eso existen las bibliotecas, manipuladas por el módulo de bibliotecas del HUB. Para que un archivo sea reconocido como un script de biblioteca por el HUB, este debe estar ubicado en la carpeta bibliotecas dentro de la ruta_raiz y usar el código Biblioteca en la segunda línea. Utilizando la función importar(biblioteca) del módulo de bibliotecas del HUB se puede obtener un nodo que tenga adjunto el script correspondiente a la biblioteca pasada por parámetro. De esta forma, se pueden utilizar todas las esctructuras y funciones que la biblioteca provee accediendo a los miembros de dicho nodo.
Godot no provee funcionalidad para manejar excepciones así que siempre que se esté modificando la funcionalidad del HUB debería ejecutarse desde el entorno de Godot y no utilizando el Core compilado. La forma de simular algo parecido al manejor de excepciones es a través de la clase Error declarada en el módulo de errores del HUB. Las funciones pueden devolver una instancia de un error en caso de que fallen por alguna razón. La función que recibe un error puede decidir qué hacer con él. La clase error sólo contiene una variable con el mensaje de error y, eventualmente, una referencia a un error previo en caso de que el error haya sido generado por un error anterior.
El módulo de errores define la función error(mensaje, stack_error=null) que devuelve un error genérico con el mensaje pasado por parámetro. Otros módulos definen otros tipos de errores como funciones. Por ejemplo, el módulo de archivos define la función archivo_inexistente(ruta, archivo, stack_error=null) que devuelve un error con el mensaje correspondiente, llamando a la función error(mensaje, stack_error=null) del módulo de errores. De esta forma, se pueden crear nuevos tipos de errores. El módulo de errores también implementa la función fallo(resultado) que determina si el resultado de un llamado a una función generó un error. Esta función puede utilizarse para verificar el resultado tras un llamado a una función que puede fallar.
El HUB también provee un módulo de testing para evaluar el correcto funcionamiento de otros scripts. La función principal de dicho módulo es test(tester, verificador, mensajes_esperados=null) que toma como parámetros dos estructuras. La primera estructura representa el test a ejecutar. Debe definir la función test(), la cual será ejecutada por el módulo de testing y sobre cuyo resultado se espera realizar una verificación. Dicha verificación debe definirse a través de la estructura pasada como segundo parámetro. Esta estructura debe definir la función verificar(resultado) la cual debe devolver un string vacío si el resultado pasado por parámetro hace que el test sea exitoso y un mensaje de error (indicando por qué no se cumplió la condición de verificación) en caso contrario. Opcionalmente se le puede pasar como tercer parámetro una lista de mensajes para verificar que la ejecución del tester produzca dichos mensajes. Opcionalmente, el verificador puede implementar la función verificar_error en caso de que se quiera verificar algo cuando se produce un error (por defecto, si se produce un error, el verificador no se ejecuta).
Cada módulo se implementa en un script, asociado a un nodo de Godot. El nodo raíz es el HUB, que tiene asociado el script HUB.gd. Este script crea un nodo por cada módulo del HUB, inserta ese nodo como hijo del nodo HUB y le asocia su script correspondiente (del mismo nombre). Todos los scripts que definen los módulos del HUB pueden verse en la carpeta src. Después de la etapa de inicialización, el nodo HUB sirve como punto de entrada para acceder a los distintos módulos. Es por esto que se toma como convención que todos los archivos que sean scripts mantengan una variable con una referencia al nodo HUB. Esta variable puede inicializarse cuando se ejecuta la función inicializar(hub) del script en cuestión. El HUB provee las siguientes funciones:
- mensaje(texto) : Envía un mensaje al HUB y lo muestra en la terminal.
- error(error, emisor="") : Notifica que se generó un error. Toma como parámetro el error generado y también lo devuelve. El error puede crearse con la función error(mensaje, stack_error=null) del módulo de errores, aunque es recomendable encapsular ese llamado dentro de una función cuyo nombre indique el tipo de error generado (ver ejemplos en los módulos que declaran errores).
- salir() : Finaliza la ejecución del HUB.
Este módulo se utiliza para acceder a los archivos dentro de la ruta_raiz. Provee las siguientes funciones:
- abrir(ruta, nombre, tipo=null) : Carga el contenido del archivo nombre. Si se le pasa como tercer parámetro un código de tipo hace todas las verificaciones necesarias para que el archivo sea un archivo válido de ese tipo y devuelve un error si no cumple alguna de esas condiciones. También puede devolver un error si el archivo no existe.
- leer(ruta, nombre) : Carga el contenido del archivo nombre y lo devuelve como texto. Devuelve un error si el archivo no existe.
- escribir(ruta, nombre, contenido, en_nueva_linea=true) : Escribe el texto contenido al final del archivo nombre. Devuelve un error si el archivo no existe. Si se le pasa false como cuarto parámetro, el nuevo texto se escribe inmediatamente a continuación del contenido existente en el archivo, es decir, sin un salto de línea antes.
- sobrescribir(ruta, nombre, contenido) : Sobrescribe el contenido del archivo archivo con el texto contenido. Devuelve un error si el archivo no existe.
- existe(ruta, nombre) : Devuelve si existe el archivo nombre.
- es_archivo(ruta, nombre) : Devuelve si el archivo nombre es efectivamente un archivo (es decir, no un directorio). Devuelve un error si el archivo no existe.
- es_directorio(ruta, nombre) : Devuelve si el archivo nombre es en realidad un directorio. Devuelve un error si el archivo no existe.
- crear(ruta, nombre) : Crea un nuevo archivo vacío con el nombre nombre en la carpeta ruta. Devuelve error si el archivo ya existe.
- crear_directorio(ruta, nombre) : Crea un nuevo directorio con el nombre nombre en la carpeta ruta. Devuelve error si el directorio ya existe.
- borrar(ruta, nombre) : Elimina el archivo nombre. Devuelve error si el archivo no existe. Si es una carpeta, elimina también todo su contenido recursivamente.
- listar(ruta, carpeta) : Devuelve una lista con los nombres de todos los archivos contenidos en la carpeta ruta. Devuelve un error si la carpeta no existe o si no es un directorio.
También declara los siguientes errores:
- archivo_inexistente(ruta, archivo, stack_error=null) : El archivo archivo no se encuentra en la ruta ruta.
- archivo_ya_existe(ruta, archivo, stack_error=null) : El archivo archivo no se puede crear porque ya existe.
- archivo_invalido(archivo, tipo, stack_error=null) : El archivo archivo no es un archivo válido de tipo tipo.
- encabezado_faltante(archivo, stack_error=null) : El archivo archivo no contiene encabezado.
- encabezado_invalido_nombre(archivo, nombre, stack_error=null) : El encabezado del archivo archivo no contiene el nombre del archivo.
- encabezado_invalido_tipo(archivo, tipo, stack_error=null) : El encabezado del archivo archivo no contiene el tipo de archivo.
- encabezado_invalido_objeto(archivo, stack_error=null) : El encabezado del archivo archivo no contiene el tipo de objeto.
- funciones_no_implementadas(archivo, tipo, stack_error=null) : El archivo archivo no implementa las funciones necesarias para ser de tipo tipo.
- no_es_un_directorio(ruta, archivo, stack_error=null) : El archivo archivo en la ruta ruta no es una carpeta.
Este módulo controla el manejo de eventos. Todos los eventos del HUB pasan por este módulo. Si un nodo quiere manejar un evento debe registrarse usando las funciones que provee este módulo y proveerle al módulo una función del nodo que será la que maneje el evento. Luego, cuando el módulo recibe un evento, notifica a través de la función provista a todos aquellos que se hayan registrado para manejar tal evento. Las funciones provistas por este módulo son las siguientes:
- registrar_press(boton, nodo, funcion) : Registra al nodo nodo para que al presionarse el boton boton se llame a la función funcion. La función utilizada no debe tomar parámetros.
- registrar_release(boton, nodo, funcion) : Registra al nodo nodo para que al soltarse el boton boton se llame a la función funcion. La función utilizada no debe tomar parámetros.
- registrar_ventana_escalada(nodo, funcion) : Registra al nodo nodo para que al modificarse el tamaño de la pantalla se llame a la función funcion. La función utilizada debe tomar un parámetro que será la nueva resolución de la ventana.
- set_modo_mouse(modo) : Asigna el modo del cursor del mouse. El valor del parámetro modo puede ser:
- 0 : Normal
- 1 : Cursor invisible
- 2 : Sin cursor (scroll infinito)
Nota: Cuando se habla de botones, puede ser una tecla del teclado o un botón del mouse, ver constantes KEY_ y MOUSE_ aquí.
Este módulo controla todo lo relacionado con la pantalla. Mantiene la variable resolucion que almacena el vector con la resolución actual de la ventana y provee las siguiente funciones:
- completa(encendido=true) : Activa o desactiva el modo pantalla completa.
- tamanio(nueva_resolucion) : Escala la ventana del HUB a la resolución nueva_resolucion.
Este módulo manipula los objetos del HUB. Provee las siguientes funciones:
- crear(hijo_de=HUB.nodo_usuario.mundo) : Crea un nuevo objeto vacío como hijo de hijo_de en la jerarquía de objetos. Si no se le pasa ningún parámetro, lo crea como hijo del objeto Mundo. Si se le pasa como parámetro null, no lo agrega a la jerarquía de objetos.
- localizar(nombre_completo, desde=HUB.nodo_usuario.mundo) : Ubica a un objeto por su nombre completo. Es decir, si se tiene un objeto Mano hijo de otro objeto que se llama Brazo, para ubicar al objeto Mano como parámtro nombre_completo se le debe pasar el texto "Brazo/Mano". Una alternativa es, si ya se tiene al objeto Brazo, pasarlo como parámetro desde para empezar a buscar a partir de él en la jerarquía de objetos. Luego, como parámetro nombre_completo se le debe pasar sólo "Mano". Notar que en el primer caso, no es necesario anteponer el nombre del Mundo ya que por defecto, es a partir de ese objeto que se empieza a buscar. Devuelve un error si no encuentra al objeto solicitado.
- borrar(nombre_completo, desde=HUB.nodo_usuario.mundo) : Borra el objeto ubicado por su nombre completo. Devuelve un error si no encuentra al objeto solicitado.
- es_un_objeto(algo) : Determina si algo es un objeto del HUB.
- agregar_comportamiento_a_objeto(objeto, nombre_script, args=[[],{}]) : Le agrega al objeto objeto el comportamiento con nombre nombre_script. Espera un par lista-diccionario como conjunto de argumentos.
- generar(nombre, args=[[],{}]) : Generea un objeto a partir del generador llamado nombre. Espera un par lista-diccionario como conjunto de argumentos.
También declara los siguientes errores:
- objeto_inexistente(nombre_completo, desde, stack_error=null) : No se encontró ningún objeto con nombre nombre_completo en la jerarquía desde el objeto desde.
- generador_inexistente(nombre, desde, stack_error=null) : No se encontró ningún generador con nombre nombre.
- comportamiento_inexistente(nombre, stack_error=null) : No se encontró ningún script de comportamiento con nombre nombre.
- mensaje_desconocido(nombre, stack_error=null) : El objeto receptor no sabe responder al mensaje nombre.
Este módulo administra las bibliotecas del HUB. Provee las siguientes funciones:
- importar(biblioteca) : Devuelve el nodo correspondiente a la biblioteca solicitada. Devuelve un error si no se encuentra.
También declara los siguientes errores:
- biblioteca_inexistente(biblioteca, stack_error=null) : La biblioteca biblioteca no se encuentra.
- biblioteca_no_cargada(biblioteca, stack_error=null) : La biblioteca biblioteca no se pudo cargar.
Este módulo implementa todo lo relacionado a la terminal del HUB. Tiene 3 submódulos. El campo de entrada para ingresar texto, El campo de mensajes que muestra la salida de la ejecución de los scripts y el nodo de comandos. Este último es el que administra los comandos cargados. Cuando se presiona la tecla Enter en el campo de entrada, el nodo de comandos busca el archivo correspondiente al comando ingresado. La terminal se puede cerrar presionando la tecla Escape o enviando un mensaje vacío (presionando la tecla Enter con el campo de entrada vacío). Cuando está cerrada, puede abrirse presionando la tecla Tab. Además de los mensajes que se muestran en el campo de mensajes, el módulo de la terminal almacena un historial completo de los mensajes enviados desde que comenzó la ejecución, incluso si estos son eliminados del campo de mensajes visibles. El campo de entrada permite autocompletar con Tab y acceder al historial con las flechas hacia arriba y abajo. Las funciones provistas por este módulo son las siguientes:
- abrir() : Abre la terminal
- cerrar() : Cierra la terminal
- ejecutar(comando_con_argumentos, mostrar_mensaje=false) : Intenta ejecutar la línea comando_con_argumentos. Devuelve un error si el comando no existe. Si se le pasa true como segundo parámetro, también envía el comando como un mensaje al HUB y lo muestra en el campo de mensajes.
- borrar_mensajes() : Limpia el campo de mensajes. Los mensajes dejan de ser visibles pero se almacenan en el historial oculto.
- log_completo(restaurar=false) : Devuelve el historial completo de mensajes. Si se le pasa true como parámetro también restaura todos los mensajes del historial y los vuelve a mostrar en el campo de mensajes.
- imprimir_entorno(activado=true) : Activa o desactiva la función de imprimir el entorno. Cuando se habilita esta opción, cada vez que se envía un mensaje al HUB, se antepone el entorno desde el cual se envió dicho mensaje.
También declara los siguientes errores:
- comando_inexistente(comando, stack_error=null) : El comando comando no existe en el HUB.
- comando_no_cargado(comando, stack_error=null) : El comando comando no se pudo cargar.
- func comando_fallido(comando, stack_error=null) : Falló la ejecución del comando comando.
Este módulo no provee ninguna funcionalidad. Sólo es el nodo raíz para todo lo creado por el usuario. Tiene dos nodos hijos. Uno es la GUI de tipo Control para crear interfaces gráficas de usuario. El otro es el objeto Mundo, raíz de la jerarquía de objetos del HUB.
Este módulo provee funciones para verificar condiciones y manejar excepciones antes de que el HUB falle. También se define la clase Error para devolver ante fallas. Las funciones provistas por este módulo son las siguientes:
- error(texto, stack_error=null) : Crea y devuelve un error genérico. Si se le pasa un segundo parámetro lo guarda como referencia al error previo.
- fallo(resultado) : Retorna si el resultado de una función generó un error
- try(nodo, funcion, parametros=[]) : Intenta ejecutar una función en un nodo. Si lo logra, devuelve el resultado de la ejecución. Si no, devuelve un error.
- verificar_implementa_funcion(nodo, funcion, cantidad_de_parametros) : Verifica que el nodo nodo implemente la función funcion con cantidad_de_parametros parámtros. Si no es así, devuelve un error.
También declara los siguientes errores:
- funcion_no_implementada(nodo, funcion, parametros, stack_error=null) : El nodo nodo no implementa la función funcion con parametros parametro(s).
- try_fallo(nodo, funcion, stack_error=null) : Falló al intentar ejecutar la función funcion en el nodo nodo.
- inicializacion_fallo(nodo, stack_error=null) : Falló al intentar inicializar el nodo nodo.
- argumento_invalido(argumento, stack_error=null) : El argumento ingresado al comando es inválido.
Este módulo administra los procesos activos. Provee las siguientes funciones:
- actual() : Devuelve el identificador (pid) del proceso actual.
- nuevo(programa, argumentos=[]) : Crea un nuevo proceso con el código del archivo programa pasándole como argumentos la lista argumentos. Devuelve un error si no se encuentra.
- todos() : Devuelve la lista de procesos activos.
- entorno(pid=null) : Devuelve el entorno de ejecución (el identificador de proceso y la secuencia de comandos actual) del proceso pid en forma de string. Si se le pasa como parámetro null, devuelve el entorno del proceso actual.
- finalizar(pid=null) : Finaliza el proceso pid. Si se le pasa como parámetro null, finaliza al proceso actual.
También declara los siguientes errores:
- programa_inexistente(programa, stack_error=null) : El programa programa no se encuentra.
- programa_no_cargado(programa, stack_error=null) : El programa programa no se pudo cargar.
- pid_inexistente(pid, stack_error=null) : No hay ningún proceso con identificador pid.
Este módulo define todo lo referente a testing. Provee las siguientes funciones:
- asegurar(condicion) : Verifica que se cumpla una condición. Genera un error si no es así.
- test(tester, verificador, mensajes_esperados=null) : Ejecuta el test definido en tester y verifica el resultado a través del verificador verificador. Si se le pasa como tercer parámetro una lista de mensajes, verifica también que los mensajes enviados por el tester coincidan con dicha lista. Retorna null si el test fue exitoso. En caso contrario devuelve el error correspondiente.
- test_genera_error(tester, error_esperado, mensaje_esperado=null) : Ejecuta el test definido en tester y verifica que genere el error error_esperado. Si se le pasa como tercer parámetro una lista de mensajes, verifica también que los mensajes enviados por el tester coincidan con dicha lista. Retorna null si el test fue exitoso (es decir, si el tester generó el error esperado). En caso contrario devuelve el error correspondiente.
- resultado_comando(comando_ingresado, verificador, mensajes_esperados=null) : Ejecuta el comando comando_ingresado y verifica el resultado a través del verificador verificador. Si se le pasa como tercer parámetro una lista de mensajes, verifica también que los mensajes enviados por el comando coincidan con dicha lista. Retorna null si el test fue exitoso. En caso contrario devuelve el error correspondiente.
- comando_fallido(comando_ingresado, error_esperado, mensaje_esperado=null) : Ejecuta el comando comando_ingresado y verifica que genere el error error_esperado. Si se le pasa como tercer parámetro una lista de mensajes, verifica también que los mensajes enviados por el comando coincidan con dicha lista. Retorna null si el test fue exitoso (es decir, si el comando generó el error esperado). En caso contrario devuelve el error correspondiente.
También declara los siguientes testers:
- tester_comando(comando_a_ejecutar) : Devuelve un tester que ejecuta el comando comando_a_ejecutar.
También declara los siguientes verificadores:
- verificador_trivial() : Devuelve un verificador que siempre devuelve true.
- verificador_nulo() : Devuelve un verificador que se asegura que el resultado sea null.
- verificador_por_igualdad(resultado_esperado) : Devuelve un verificador que se asegura que el resultado sea igual a resultado_esperado.
- verificador_error(error_esperado) : Devuelve un verificador que se asegura que el resultado sea un error de tipo error_esperado.
También declara los siguientes errores:
- condicion_fallida(stack_error=null) : La condición que se quería asegurar resultó ser falsa.
- test_fallido_resultado(stack_error=null) : El test falló porque el resultado no cumplió la condición del verificador.
- test_fallido_salida(stack_error=null) : El test falló porque los mensajes enviados no fueron los esperados.
- test_fallido_error(stack_error=null) : El test generó un error inesperado.
En la carpeta plantillas dentro de la ruta_raiz hay plantillas base de cada tipo de archivo. También se pueden ver los archivos ya existentes en cada carpeta para usar como guía. Excepto archivos con propósitos específicos o archivos multimedia, todos los archivos del HUB deben contener en la primera línea doble #, un espacio y el nombre del archivo y en la segunda línea doble "#", un espacio y el código que indica el tipo de archivo. Opcionalmente, la cuarta línea puede contener un #, un espacio y una descripción del archivo.
Como los scripts deben ser scripts válidos de GDScript, deben tener la línea "extends Node" para que Godot pueda adjuntárselos a un nodo. Para ser scripts válidos del HUB, deben implementar la función inicializar(hub), la cual será llamada con el HUB como parámetro cuando el script sea agragado a un nodo de Godot. No es obligatorio pero se recomienda mantener la variable HUB (e inicializarla con el parámetro hub) para acceder al HUB y a todos sus módulos. Esta función debería devolver null a menos que se genere un error. En dicho caso, debería devolver el error generado y se asume que el script que lo creó puede eliminar al nodo correspondiente ya que este quedó en un estado inconsistente. En el caso de los scripts fuente, dado que el módulo de errores podría no haberse inicializado todavía, esta función devuelve un booleano que indica si el script se inicializó correctamente.
Para ser un comando válido, el script debe implementar la función comando(argumentos). El parámetro argumentos se debe interpretar como un conjunto de argumentos (ver apéndice). Esta función puede devolver un resultado para ser entregado al script que solicitó la ejecución del comando. Opcionalmente, puede implementar las funciones descripcion() que devuelva una descripción corta del comando y man() que devuelva el manual completo del comando. El código para archivos de comandos es Comando.
Para ser un programa válido, debe implementar la función inicializar(hub, pid, argumentos) en lugar de inicializar(hub). También debe implementar la función finalizar() que será llamada por el módulo de procesos cuando el proceso sea terminado. El programa no debería llamar a su propia función de finalización, ya que esta será llamada por el módulo de procesos cuando se invoque a la función finalizar(pid) de dicho módulo. La función finalizar() de un programa debería encargarse de eliminar todos los nodos, datos y objetos creados por dicho programa, así como cancelar las suscripciones a eventos correspondientes. Por defecto, el HUB no hace nada de eso automáticamente. El código para archivos de programas es Programa.
Las bibliotecas no tienen restricciones adicionales. El código para archivos de bibliotecas es Biblioteca.
Los scripts de objetos pueden ser de varios tipos. Como todos los archivos del HUB tienen la extensión .gd la forma de diferenciarlos es a través de la tercera línea del encabezado. El código para los archivos de objetos es Objeto.
Una forma de definir objetos es a través del lenguaje de generación de objetos HUB3DLang. El código para los archivos que definen objetos de esta forma es HUB3DLang. Al estar escrito en dicho lenguaje, los scripts de este tipo no pueden implementar funciones (ni siquiera inicializar) ni variables (ni siquiera HUB).
Los objetos también se pueden definir con funciones. El código para los archivos que definen objetos de esta forma es Funcion. A diferencia de los archivos de HUB3DLang, estos archivos sí son scripts de godot y deben implementar la función inicializar. Además, deben implementar la función gen que a partir de un conjunto de argumentos (ver apéndice) devuelve el objeto generado.
Para ser un script de comportamiento válido, debe implementar la función inicializar(hub, yo, args) en lugar de inicializar(hub). El argumento yo se corresponde con el objeto al cual se le está asignando el comportamiento. El argumento args se corresponde con el conjunto de argumentos (ver apéndice) opcionales que se le pasaron al script al ser creado.
Los lotes de comandos no tienen restricciones adicionales. Cada línea debería ser un comando válido aunque eso no se verifica hasta que cada línea se intenta ejecutar. El código para archivos de lotes de comandos es SH. Si el HUB tiene el comando sh y el lote de comandos INI.gd en la carpeta shell, tras haberse inicializado ejecutará "sh INI.gd" en la terminal.
Los scripts se ejecutarán como scripts de GDScript dentro del entorno de Godot. Esto significa que se pueden utilizar todas las herramientas provistas por el motor de Godot. Sin embargo, los módulos del HUB también hacen uso de estas herramientas y podrían entrar en conflicto con ellos. Por eso se recomienda en cambio utlizar las abstracciones del HUB para acceder a las herramientas de Godot. Algunos ejemplos:
- No utilizar la funciones _ready() o _init(). Para todo lo que sea inicialización de una sóla vez, usar la función inicializar(hub). Esta es llamada luego de que el nodo es agregado al árbol de nodos de Godot.
- No utilizar las funciones _process(delta) o _fixed_process(delta). En su lugar, suscribirse a los eventos temporales con HUB.input.registrar_tiempo(nodo, funcion).
- No utilizar las funciones _input(event) o _input_event(event). En su lugar, suscribirse a los eventos de input con HUB.input.registrar_press(boton, nodo, funcion), HUB.input.registrar_release(boton, nodo, funcion), etc.
- Al trabajar con objetos, recordar que un objeto no es equivalente a un nodo de Godot. Por lo tanto,
- No crear ni destruir objetos con las funciones de nodos (New(), free(), etc). En su lugar, usar HUB.objetos.crear(), HUB.objetos.borrar(objeto), etc.
- no acceder ni modificar la jerarquía de objetos con las funciones de nodos de Godot (get_node(path), get_children(), add_child(node), etc). En su lugar, usar HUB.objetos.localizar(nombre_completo), hijos(), agregar_hijo(objeto), etc.
- No utilizar las clases File o Directory para acceder a archivos. En su lugar, usar HUB.archivos.abrir(ruta, nombre), HUB.archivos.leer(ruta, nombre), etc.
Algunos tipos de scripts, (en particular los comandos, los comportamientos y los objetos de tipo función) implementan funciones que toman un conjunto de argumentos como parámetro. Esta estructura es (en pricipio) una lista de dos elementos. El primer elemento de la lista es una lista de argumentos. El segundo elemento es un diccionario. Ambos elementos pueden contener información. En el caso de los comandos, el diccionario contiene un par clave-valor por cada argumento iniciado en "-" que se pasó al ejecutar el comando en cuestión. La clave es el primer caracter después del "-" y el valor es el resto. Aquellos argumentos que no comienzan con "-", van a parar a la lista en el orden en que aparecen. En el caso de los comportamientos y objetos de tipo función, el diccionario contiene un par clave-valor por cada argumento que contenga el caracter "=" pasado al definir el comportamiento u objeto en cuestión. La clave es el texto previo al "=" y el valor es el texto posterior al mismo. Aquellos argumentos que no incluyan el caracter "=", van a parar a la lista en el orden en que aparecen. En cualquier caso, se puede definir una variable arg_map en en script para que en lugar de recibir los argumentos de esta forma, los reciba como un diccionario más cómodo. La variable arg_map debe ser un diccionario que incluya el campo lista con una lista que determina los posibles argumentos. Además, puede definir el campo booleano extra si admite una secuencia ilimitada de argumentos o el campo numérico obligatorios para indicar que los primeros argumentos de la lista son obligatorios. Cada elemento en la lista debe ser a su vez un diccionario que defina los campos nombre y codigo, siendo codigo exactamente un caracter para identificar el argumento. Además, puede definir el campo default para que tenga un valor por defecto cuando este argumento no se pasa explícitamente. Si no se define este campo, se inicializa con null. También se puede definir el campo validar cuyo valor será un string con una o más condiciones que debe cumplir el valor del argumento, separadas con ";". Las posibles validaciones son: BOOL (debe ser un flag), NUM (debe ser un número), INT (debe ser entero), DEC (debe ser una fracción), >x (mayor a un número x), <x (menor a un número x), >=x (mayor o igual a un número x), <=x (menor o igual a un número x). Al pasarle argumentos a estos scripts se pueden usar tanto el código de una letra como el nombre completo pero en el diccionario de argumentos resultante todos los argumentos se indexan por el código. En el caso de los comandos, para usar el código de una letra se puede escribir el valor a continuación del código. Para usar el nombre, se debe intercalar el caracter "=" entre el nombre del argumento y el valor.
Ejemplos: #TODO!