- pyphiverses -
Entre ganchos y cadenas
Desmontando la attoMOLE

Si no leiste la introducción a pyphiverses, puede ser un buen momento para hacerlo.

Todo lo que tiene un principio,
... tiene un final.

Vamos a empezar a codificar... Si no se empieza, no se acaba.

Se trata de cuestiones técnicas que a un autor de Ficción Interactiva pueden no interesarle en principio, y no es necesario que las comprenda a nivel profundo, pero si es deseable que se vaya familiarizando con ciertos conceptos y detalles, aunque sea solo de oidas.

A aquéllos que deseen participar en el desarrollo de pyphiverses les será necesario comprender estas interioridades. Se empieza con algo sencillo, no os preocupéis.

Si teneis alguna duda sobre el tema aquí tratado podéis utilizar el hilo en el foro del CAAD enlazado al final de este documento. Os responderemos gustosamente.

Dicho esto, desmontemos attoMOLE...

AttoMOLE

En palabras de su creador, Zak McKracken:

4. Qué ofrece
=============

Básicamente dos cosas:

* Funciones de E/S de bajo nivel (basadas en Glk)
* Mecanismo genérico de Ganchos

El mecanismo genérico de ganchos es el que lo hace extremadamente flexible y ampliable a base de módulos.

Zak diseñó attoMOLE para paliar determinadas deficiencias en la gestión de ganchos por parte de Inform, ya que éste utiliza rutinas para ellos. Los problemas de utilizar rutinas se ponían especialmente de manifiesto en la creación de librerías, sobre todo las que utilizan Glk. (La documentación de attoMOLE describe los problemas detalladamente).

Este documento trata la parte del "mecanismo genérico de ganchos" y no sobre Inform o Glk. El concepto de cadena se aprovechará en la creación de acciones en pyphiverses, uso que se detallará en subsiguientes documentos, demostrando su flexibilidad.

La idea de gancho se divide en dos conceptos íntimamente ligados...

En attoMOLE, una función puede interrumpir la ejecución de la cadena en la que se encuentra según el valor que devuelva. Con un parámetro se indica, en el momento de añadirse la función a la cadena, si True ó False, siempre o nunca, dependiendo de una constante.

BLOQUEAR_NUNCA BLOQUEAR_SI_TRUE BLOQUEAR_SI_FALSE ó BLOQUEAR_SIEMPRE

Puede recibir parámetros extra. Además se permite la inserción dentro de la cadena en una posición determinada, con

GANCHO_EN_POSICION_POR_DEFECTO GANCHO_EN_PRIMERA_POSICION GANCHO_EN_ULTIMA_POSICION ó GANCHO_EN_POSICION_DADA

Deconstrucción simplificada alternativa

Se propone la implementación de una "cadena de responsabilidad compartida" que no contempla dos de las características de attoMOLE. La inserción en puntos intermedios y el paso de parámetros extra a las funciones, ya que no son necesarias o deseables en esta fase del proyecto.

La inserción en puntos intermedios puede provocar "peleas" entre las distintas funciones que se enganchan a la cadena del hook (varias pueden querer ser la primera, la útima, o la enésima), aumentando la complejidad del sistema y generando cierto indeterminismo, cuando aquí se pretende lo contrario. La pelea la gana el último, dentro del orden del código fuente, que solicite una posición.

El paso de parámetros puede ser sustituido por "propiedades" (miembros) de clase cuando sea necesario.

Se amplía la capacidad de comunicación con el exterior, de forma que se pueda obtener información sobre el éxito o fracaso de cada elemento de la cadena más flexiblemente, gracias a la asignación de múltiples posibles valores de devolución (no solo True ó False), lo que permitirá conocer los motivos exactos de parada o continuación.

Muestra de la implementación

La clase FunctionContext representa el tipo de objeto de los elementos que se pueden añadir a una cadena. Contiene dos funciones en sus miembros. En Python, una función es un objeto más, y puede "almacenarse" (guardar una referencia de ella) como cualquier otro tipo de dato, en una "variable".

class FunctionContext(): def __init__(self, function, check_callback): self.function = function # check_callback debe poder recibir un parámetro, # algo como: # def function_check(result_to_check) self.check_callback = check_callback

function es una función en Python, que se ejecutará cuando le llegue su turno a lo largo de la ejecución secuencial de la cadena.

check_callback es otra función que devolverá True o False dependiendo de si el valor pasado (el devuelto por .function()) permite la continuación de la ejecución de la cadena o si, por el contrario, detiene la misma. Su existencia permite tener varias situaciones "positivas" y/o "negativas", que en instancias superiores de la aplicación tendrán significados diferentes y concretos. Por ejemplo, no se puede coger un objeto porque pesa mucho, porque el inventario está lleno, porque no es un objeto que se pueda coger, etc... Esta lógica depende del uso concreto de cada cadena y por eso se delega su comprobación a la función .check_callback().

Veamos ahora la clase FunctionChain, que implementa la cadena:

Se utiliza una implementación sencilla, sin utilizar herencias ni declarar la clase de "tipo" callable. Ambas mejoras se dejarán para la implementación definitiva y se omiten aquí por claridad.

class FunctionChain(): def __init__(self, name): # Un nombre que identifica la cadena. self.name = name # Una lista para guardar los elementos ordenados # que componen la cadena. self.chain = [] # Result List # Contiene los resultados de las llamadas # a todos los elementos de la cadena, en # el mismo orden que se ejecutan. self.rl = [] def append_function_context(self, context): # Añade un nuevo elemento a la cadena en # la última posición de la misma. self.chain.append(context) def run(self): # Ejecuta secuencialmente todos los elementos # de la cadena. # Se vacia la Result List self.rl = [] # Cada elemento... for f_context in self.chain: # ... se ejecuta... result = f_context.function() # ... se añade el resultado a la rl... self.rl.append(result) # ... y se comprueba si se puede continuar... # ... mediante la función proporcionada... # ... a tal efecto... if( f_context.check_callback(result) != True ): # ... si no se puede continuar, se termina # ... la cadena. return False # Llegados aquí, todos los elementos se han ejecutado. return True

La linea if( f_context.check_callback(result) != True ) es la que se encarga de llamar a la función callback de comprobacion check_callback. Si la callback da el visto bueno (devuelve True) la cadena continua. Si no lo da, la cadena se corta, ejecutandose el return False.

Creando un nuevo objeto derivado de FunctionChain, al que se le añaden uno o varios FunctionContext via .append_function_context() podemos definir en la aplicación un hook incluyendo una linea que llame a .run(). Éste nos devolverá True si la cadena se completó o False si se detuvo por algún motivo.

La información sobre la ejecución de cada uno de los elementos de la cadena estará disponible en .rl de la FunctionChain.

Dudas y contacto

En siguientes entregas ejemplificaremos el uso de estas clases.

Hasta entonces, como dijimos al principio, estamos a vuestra disposición, atendiendo dudas y comentarios sobre esta "cadena de responsabilidad compartida" en este hilo del foro del CAAD.

Gracias por la atención prestada.
dddddd.-