Creación masiva de clases de C++ (u otros lenguajes) mediante plantillas

Esta tarde estaba haciendo pruebas y he tenido que crear unas 30 o 40 clases, por tanto, necesitaba automatizar y mejorar el proceso. Con Visual Studio el tema de crear clases lo veo un infierno, con eclipse la cosa mejora, pero sigue existiendo el problema.

Por tanto os pongo este script que con solo preguntaros 2 cosas por clase, podeis crear clases a gran velocidad. Es obligatorio que cambieis las plantillas a vuestras necesidades. Se podría uasar para cualquier lenguaje, ya que la generación del nombre del fichero es otra plantilla en sí. Queda pendiente sacar la plantilla a un archivo externo. Hay que decir que lo he hecho en menos de 1 hora.

Aquí lo pego, aunque por si se ve mal también dejo un pastebin:

#!/usr/bin/python
# -*- coding: utf-8 -*-
# vim: set fileencoding=utf-8 :
# 
# Script para generar clases de C++ agilmente
# Requerido: Python 3.x
#
# 1ª vez de uso se creará un script que debe de configurarse según las necesidades
# Esta configuración es global: directorio de salida, autor y formato fecha
# En siguientes ejecuciones solo preguntará interacticamente y en bucle:
# - Nombre de clase
# - Proposito
#
# Para dejar de crear clases poner un nombre de clase en blanco
#
# Las plantillas deben modificarse y adaptarse a las necesidades de cada uno.
# Queda pendiente sacar las plantillas a fichero
#

import os, sys
import configparser
from datetime import date
d = date.today()

# global
TODIR = "."
NAMESPACE = "Dune"
AUTOR = "Ricardo Marmolejo García"
FORMAT_FECHA = "%d/%m/%y"
config_file = "create_class.cfg"

if(not os.path.exists(config_file)):
    # escribir una config por defecto
    config = configparser.ConfigParser()
    config.add_section('create_class')
    config.set('create_class', 'TODIR', "%s" % TODIR)
    config.set('create_class', 'NAMESPACE', "%s" % NAMESPACE)
    config.set('create_class', 'AUTOR', "%s" % AUTOR)
    config.set('create_class', 'FORMAT_FECHA', "%s" % FORMAT_FECHA)

    f = open(config_file, 'w')
    config.write(f)
    f.close()
    
    print("Se ha creado una configuración por defecto en %s." % config_file)
    print("Condigurelo, y vuelva a ejecutar el script.")
    sys.exit(1)

else:
    
    config = configparser.ConfigParser()
    config.read(config_file)
    
    TODIR = config.get('create_class', 'TODIR')
    NAMESPACE = config.get('create_class', 'NAMESPACE')
    AUTOR = config.get('create_class', 'AUTOR')
    FORMAT_FECHA = config.get('create_class', 'FORMAT_FECHA')

# obtener fecha, en el formato especificado
FECHA = d.strftime(FORMAT_FECHA)

salir = False

while not salir:
    
    CLASSNAME = input("Escribe el nombre de la clase (dejelo en blanco si quiere terminar): \n")
    
    if CLASSNAME == "":
        salir = True
        break
    
    PROPOSITO = input("Escriba el propósito de esta clase: \n")
    print("\n")

    '''
    Plantillas:
    
        - template_cpp_namefile: Plantilla que genera el nombre del archivo cpp
        - template_h_namefile: Plantilla que genera el nombre del archivo .h
    
    Campos disponibles:
    
    __NAMESPACE__ = Nombre del namespace original
    __NAMESPACE_UPPER__ = Nombre del namespace en mayusculas (puntos pasan a ser guiones bajos)
    __NAMESPACE_LOWER__ = Nombre del namespace en minusculas (puntos pasan a ser guiones bajos)
    __CLASSNAME__ = Nombre de Clase original
    __CLASSNAME_UPPER__ = Nombre de Clase en mayusculas (puntos pasan a ser guiones bajos)
    __CLASSNAME_LOWER__ = Nombre de Clase en minusculas (puntos pasan a ser guiones bajos)
    '''
    template_cpp_namefile = "__CLASSNAME__.cpp"
    template_h_namefile = "__CLASSNAME__.h"

    '''
    
    Plantillas:
    
        - template_cpp: Plantilla que genera el contenido del cpp
        - template_h: Plantilla que genera el contenido del h
    
    Campos disponibles:
    
    __NAMESPACE__ = Nombre del namespace original
    __NAMESPACE_UPPER__ = Nombre del namespace en mayusculas (puntos pasan a ser guiones bajos)
    __NAMESPACE_LOWER__ = Nombre del namespace en minusculas (puntos pasan a ser guiones bajos)
    __CLASSNAME__ = Nombre de Clase original
    __CLASSNAME_UPPER__ = Nombre de Clase en mayusculas (puntos pasan a ser guiones bajos)
    __CLASSNAME_LOWER__ = Nombre de Clase en minusculas (puntos pasan a ser guiones bajos)
    __FILE__ = Nombre del archivo original
    __FILE_UPPER__ = Nombre del archivo en mayusculas (puntos pasan a ser guiones bajos)
    __FILE_LOWER__ = Nombre del archivo en minusculas (puntos pasan a ser guiones bajos)
    __PROPOSITO__ = Proposito de la clase
    __AUTOR__ = Autor de la clase
    __FECHA__ = Dia, Mes y año del momento de la creación
    '''

    template_h = """//---------------------------------------------------------------------------
// __FILE__
//---------------------------------------------------------------------------

/**
@file __FILE__

__PROPOSITO__

@author __AUTOR__
@date __FECHA__
*/

#ifndef __FILE_UPPER__
#define __FILE_UPPER__

#include "../Component.h"

namespace __NAMESPACE__ {

class __CLASSNAME__ : public Component
{
public:
    __CLASSNAME__();
    virtual ~__CLASSNAME__();
    
public:

    // SERVICIOS DE LA COMPONENTE
    

public:

    /**
    Ejecutado en su primer tick
    */
    virtual void OnCreate();

    /**
    Actualizado intensivamente
    */
    virtual void Update();

    /**
    Actualizado en una frecuencia fija
    */
    virtual void UpdateFixed();

    /**
    Ejecutado antes de su destrucción
    */
    virtual void OnDestroy();

};

} // end namespace __NAMESPACE_UPPER__

#endif __FILE_UPPER__
"""

    template_cpp = """//---------------------------------------------------------------------------
// __FILE__
//---------------------------------------------------------------------------

/**
@file __FILE__

__PROPOSITO__

@author __AUTOR__
@date __FECHA__
*/

#include "../Common.h"
#include "__CLASSNAME__.h"
#include "../Entity.h"

namespace __NAMESPACE__ {

__CLASSNAME__::__CLASSNAME__()
{
    
}

__CLASSNAME__::~__CLASSNAME__()
{
    
}

void __CLASSNAME__::OnCreate()
{
    
}

void __CLASSNAME__::Update()
{
    
}

void __CLASSNAME__::UpdateFixed()
{
    
}

void __CLASSNAME__::OnDestroy()
{
    
}

} // end namespace __NAMESPACE_UPPER__
"""

    # init vars
    NAMESPACE_UPPER = NAMESPACE.upper()
    NAMESPACE_LOWER = NAMESPACE.lower()
    CLASSNAME_UPPER = CLASSNAME.upper()
    CLASSNAME_LOWER = CLASSNAME.lower()
    NAMESPACE_UPPER = NAMESPACE_UPPER.replace(".", "_")
    NAMESPACE_LOWER = NAMESPACE_LOWER.replace(".", "_")
    CLASSNAME_UPPER = CLASSNAME_UPPER.replace(".", "_")
    CLASSNAME_LOWER = CLASSNAME_LOWER.replace(".", "_")

    for newfile in [    [template_cpp_namefile, template_cpp],
                        [template_h_namefile, template_h]       ]:

        FILE = newfile[0]
        FILE = FILE.replace("__NAMESPACE__", NAMESPACE)
        FILE = FILE.replace("__NAMESPACE_UPPER__", NAMESPACE_UPPER)
        FILE = FILE.replace("__NAMESPACE_LOWER__", NAMESPACE_LOWER)
        FILE = FILE.replace("__CLASSNAME__", CLASSNAME)
        FILE = FILE.replace("__CLASSNAME_UPPER__", CLASSNAME_UPPER)
        FILE = FILE.replace("__CLASSNAME_LOWER__", CLASSNAME_LOWER)
        FILE_UPPER = FILE.upper()
        FILE_LOWER = FILE.lower()
        FILE_UPPER = FILE_UPPER.replace(".", "_")
        FILE_LOWER = FILE_LOWER.replace(".", "_")

        generated_file = os.path.join(TODIR, FILE)
        
        if not os.path.exists(generated_file):

            VALUE = newfile[1]
            VALUE = VALUE.replace("__NAMESPACE__", NAMESPACE)
            VALUE = VALUE.replace("__NAMESPACE_UPPER__", NAMESPACE_UPPER)
            VALUE = VALUE.replace("__NAMESPACE_LOWER__", NAMESPACE_LOWER)
            VALUE = VALUE.replace("__CLASSNAME__", CLASSNAME)
            VALUE = VALUE.replace("__CLASSNAME_UPPER__", CLASSNAME_UPPER)
            VALUE = VALUE.replace("__CLASSNAME_LOWER__", CLASSNAME_LOWER)
            VALUE = VALUE.replace("__FILE__", FILE)
            VALUE = VALUE.replace("__FILE_UPPER__", FILE_UPPER)
            VALUE = VALUE.replace("__FILE_LOWER__", FILE_LOWER)
            VALUE = VALUE.replace("__PROPOSITO__", PROPOSITO)
            VALUE = VALUE.replace("__AUTOR__", AUTOR)
            VALUE = VALUE.replace("__FECHA__", FECHA)

            file_cpp = open(generated_file, "w", encoding='utf-8')
            file_cpp.write(VALUE)
            file_cpp.close()
            
            print("%s se ha creado correctamente" % FILE)
            
        else:
            
            print("%s ya existe!, no se ha creado." % FILE)
            
    print("\n")

Eclipse instalando el plugin de Android (ADT) en Linux

Si habeis instalado Eclipse mediante el Centro de Software Ubuntu, puede que tengais problemas siguiendo el procediemiento que explica Google.

Por tanto, probar hacerlo mejor así:

  1. Se supone que ya teneis instalado el paquete eclipse, instalar además el paquete: sudo apt-get install eclipse-jdt eclipse-pde , tal y como explica el Bug #477944 (aunque esta un poco caótico).
  2. Ahora debeis ejecutar Eclipse y en Help -> Install new software, debeis añadir estos 2 sitios:
  3. Ahora simplemente instalais «Developers Tools», que activará automaticmante «Android DDMS» y «Android Development Tools». Tendreis que aceptar todas las licencias que corresponda.
  4. Fin, ya podemos crear un proyecto. Tengo pensado publicar un trabajo que estoy haciendo de Android y explicar un poco hacer un HolaMundo en cuanto tenga tiempo.

Espero que le sea útil a alguien. Un saludo

Fuentes:
http://developer.android.com/sdk/eclipse-adt.html
https://bugs.launchpad.net/ubuntu/+source/eclipse/+bug/482244
https://bugs.launchpad.net/ubuntu/+source/eclipse/+bug/477944

Benchmark Factorial – Parámetros acumulados

Voy hacer un benckmark de 3 implementaciones de factorial:

Em primer lugar, la implementación más simple y eficiente, por siempre de los jamases, al menos para arquitecturas de Von Newman y monoprocesador:

long factorial_iterativo(long n)
{
	long i, ac = 1;
	for(i=n; i>0; i--)
		ac = ac * i;
	return ac;
}

Despues esta la recursiva lineal, típico ejemplo de recursividad. Fijaos en la linea de la llamada recursiva …

long factorial_recursivo(long n)
{
	if (n == 0)
		return 1;
	else
		return n * factorial_recursivo(n-1);
}

Y esta es la que realmente tenía curiosidad de comparar.  Es un tipo de recursividad que hace que la vuelta en lugar de ser de pila, sea de cola. Es necesario modificar la interfaz y crear una nueva función manejadora que respete la interfaz. Se trata de ir acumulando el resultado como parametro y principalmente que en la linea que se hace la llamada recursiva, solo haya una llamada recursiva.

Esta función, teoricamente implementado en Caml el compilador al pasarlo a código maquina la pasa a iterativa automaticamente. Si esto fuera tambien así en C, podríamos utilizar funciones recursivas, sin miedo a desbordar la pila y sin problemas de redimiento. Y por supuesto ganando la claridad de resolver algunos problemas de naturaleza recursiva.

long _factorial_recursivo_PA(long n, long r)
{
	if (n==0)
		return r;
	else
		return _factorial_recursivo_PA(n-1, n*r);
}

long factorial_recursivo_PA(long n)
{
	return _factorial_recursivo_PA(n, 1);
}

Los resultados son estos:

Seguir leyendo

Señal SIGCHLD – Evitar Zombies

Hace un par de años hice una práctica de sockets, con el típico modelo cliente-servidor donde el servidor da servicio multicliente con fork(). La práctica cumplía los requisitos y los profesores no detectaron problemas.

Realmente la práctica tenía un bug y es que en ocaciones, se quedaban procesos zombies. Realmente el problema es que confie en el código base que nos daban los profesores, que estaba bugeado. Ahora he tenido que reutilizarla para otra asignatura, y ahora ya se cual era el problema.

En sistemas Unix, un proceso puede tener hijos ¬¬, creados mediante fork. Cuando el hijo termina, se envía la señal SIGCHLD al padre. Por defecto, la señal se ignora y se crea un proceso zombie. El padre debe instalar un manipulador de señales para actuar sobre la señal, ese era mi problema. En algunas plataformas Unix, se pueden evitar los zombies explícitamente ignorando la señal SIGCHLD. Sin embargo, instalar un manipulador de señales para SIGCHLD y llamar a wait es la mejor manera de evitar zombies conservando la portabilidad.
El manipulador de la señal:

void sigchld_handler(int s)
{
    while(wait(NULL) > 0);
}

Hay varias funciones o métodos para setear la señal, os pongo 2, el largo(más portable, POSIX) y el corto(desconozco su portabilidad):

    struct sigaction sa;
    sa.sa_handler = sigchld_handler; // manejador

    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGCHLD, &sa, NULL) == -1)
    {
        perror("Error en sigaction");
        exit(1);
    }

Os dejo la forma más corta y fácil:

signal(SIGCHLD , sigchld_handler);

No hace falta decir que necesitais el «signal.h»

Fuentes: http://es.wikipedia.org/wiki/SIGCHLD

Procesar y borrar elementos de una lista en O(n)

Voy a explicar que elección de diseño he elegido en mi juego para la gestión de la lista de entidades.

En todo videojuego hay una lista de entidades que necesitan hacer en su máximo nivel de abstracción 2 cosas:

  1. Procesar todos los elementos. Esto es que cada elemente tiene un metodo, tipicamente refrescar() o update() que abstrae toda la secuencia de proceso que tiene que hacer.
  2. Borrar los elementos que han muerto o a terminado su ciclo de vida.

Poder borrar el elemento de lista me dificulta la implementación, asi que he optado por que el nodo tenga un método en el que solicita la liberación. Cuando llege su turno será eliminado. Esto tambien lo hago así por que physX exige borrar sus entidades en puntos muy concretos de muy código. Cuando no lo hacía así cada 5 min de juego tenía un «segmentation fault» que me crasheaba el juego.

Hay infinitos ejemplos por los que hay que borrar una entidad. Un bot que ha cumplido su objetivo.
Por ejemplo, suponer que una entidad (un proyectil en concreto) que se mueve, esto lo hace durante el refresco, se sale del mapa, cuando se salga queremos borrarlo. Por tanto en pseucódigo será.

if(estoyDentroDeLimites())
	avanzar();
else
	pedirLiberacion();

Cuando a otro nivel llamamos al proceso de las entidades, el proceso debería quedar algo así.

void procesarSeleccionados ( list<Nodo*> * lista )
{
	Nodo * aux = NULL;
	list::iterator it = lista->begin();
	while(it != lista->end())
	{
		aux = *it;
		if(aux->getPedirLiberacion() && aux->liberar())
		{
			delete aux;
			it = lista->erase(it);
		}
		else
		{
			aux->refrescar();
			i++;
		}
	}
}

Estoy utilizando una lista del STD.  Comentar:

  • El if() podríamos sustituirlo por la condición de borrado. En este ejemplo significa:
    • si quieres que te borren. getPedirLiberacion()
    • Y te has borrado correctamente. && aux->liberar()
  • La función erase() borra a lo que apunta el iterador y automaticamente te avanza el iterador, teniendo siempre un iterador válido. Por eso el i++ esta en el else, si lo pusioramos fuera nos saltariamos un elemento de la lista cada vez que borremos.

Sockets : Timeout en Send() y Recv()

Bueno he encontrado una forma más fácil de hacer un timeout en los socket(anteriormente usaba la técnica de las alarmas, en total hay 3 técnicas para evitar el cuelgue indefinido), para evitar que se queden colgados en los send() y en los recv().

Tras crear el socket ponemos(para un timeout de 15 segundos por ejemplo):

//Si es TCP
if ((descriptor=socket(AF_INET, SOCK_STREAM, 0))==-1) throw Excepcion(«Error al crear el socket TCP»);
//Si es UDP
//if ((descriptor=socket(AF_INET, SOCK_DGRAM, 0))==-1) throw Excepcion(«Error al crear el socket UDP»);

//Definimos el timeout
struct timeval tv;
tv.tv_sec = 15;
tv.tv_usec = 0;
setsockopt(descriptor, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(descriptor, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));

Ejecutar ASP .NET con MonoDevelop

Esta pequeño tutorial ha sido probado en ubuntu pero en otras distribuciones de linux no debería ser muy distinto.

Primero instalamos todos los paquetes relacionados con mono o monodevelop :

  • sudo apt-get install mono mono-gmcs mono-utils monodevelop monodoc mono-xsp monodoc-http monodoc-ipod-manual monodoc-njb-manual monodoc-nunit-manual monodoc-gtk2.0-manual monodoc-gecko2.0-manual monodoc-ipod-manual monodoc-njb-manual monodoc-nunit-manual monodoc-gecko2.0-manual mono-xsp2 monodevelop-java libnunit-doc monodevelop-nunit monodevelop-versioncontrol

Si os falta algún paquete, o teneis algún problema de dependencias, probar a poneros el repositorio de getdeb, que tiene versiones más actualizadas de mono y monodevelop:

  • echo deb http://ubuntu.org.ua/ getdeb/ | sudo tee -a /etc/apt/sources.list
  • Actualizamos la lista de repositorios : sudo apt-get update

Si quieres, instalate los ejemplos para ASP.NET 1.1 y 2.0:

  • sudo apt-get install asp.net-examples asp.net2-examples
  • El ejemplo de 2.0 tiene un bug tonto que se arregla : sudo cp /usr/share/asp.net2-demos/index.aspx /usr/share/asp.net2-demos/index2.aspx
  • Para ver el ejemplo nos vamos a:
    • nos vamos al directorio de trabajo que corresponde : cd /usr/share/asp.net-demos/
    • Ahora simplemente ponemos en consola xsp2
    • Nos monta un servidor donde podemos ver el ejemplo : http://127.0.0.1:8080/
    • El ejemplo de asp 1.1 es igual pero esta en cd /usr/share/asp.net-demos/y ejecutando xsp
    • Además del server de testing que montas sobre el actual directorio de trabajo en el puerto 8080, los paquetes mono-xsp y mono-xsp2 montan 2 demonios en el 8081 y el 8082 respectivamente. Yo los he dejado pero sabeis que podeis configurar los opciones por defecto de un demonio en /etc/default. En concreto en /etc/default/mono-xsp y /etc/default/mono-xsp2 respectivamente.

El ejemplo es poco educativo,así que vamos hacer un ejemplo para ir empezando:

  • Empezamos un proyecto en MonoDevelop:
monodevelop1
  • En Default.aspx escribimos entre las etiquetas body:
monodevelop2
  • En Default.aspx.cs detectamos el evento del boton:
monodevelop3
  • Ya esta ahora tenemos que construir el proyecto cada vez que modifiquemos algo. El ejecutar solo le damos la primera vez para montar el server xsp2 en http://localhost:8080/ y las siguientes veces el ciclo de desarrollo sera: 1º Realizar cambio, 2º Construir el proyecto y 3º Actualizar con F5 sobre la pestaña de firefox que corresponda :P
  • Por último, os explico alguna conceptos que pueden seros útil para ir empezando, tanto en visual studio como en monodevelop una solución es un conjunto de proyectos, mientras que un proyecto es un conjunto de paginas (en ASP.NET), por eso hay un botón para construir proyecto y otro para construir solución.
  • Una página tiene por un lado la vista(Default.aspx) y por otro lado la lógica de la pagina.
  • Los IDE suelen autogenerar parte de la lógica. Por ello la lógica a su vez se divide en dos.
  • Lógica para que el usuario añada a eventos concretos de la página(Default.aspx.cs), cuando empieza la carga, se pulsa un botón, empieza a escribir …,
  • La lógica autogenerada por el IDE (Default.designer.aspx.cs), que en el caso de MonoDevelop es editable.Estas dos páginas son la misma clase definida en 2 partes, que es una de las novedades de .NET 2.0, la inclusión de clases parciales. Por tanto las variables que añade automaticamente en Default.designer.aspx.cs tienen ambito visible desde Default.aspx.cs y nos permite hacer lo del ejemplo.
  • Nota: Si en la vista borrais un boton asp u otro objeto asp(borrando el html a pelo) que esta identificado por un id, y por tanto tiene una variable en Default.designer.aspx.cs, vereis que la variable no se borra. Parece que la versión de MonoDevelop 0.16 que uso solo añade lógica autogenerada pero nunca borra. La solución es simplemente borrar todas las variables que ha autodefinido y cuando construyas la solución o el proyecto se generaran las variables que falten.
  • Nota 2: Es imprescindible el uso de monodoc (además de google, no hace falta decirlo xD)
  • Hoy ha salido MonoDevelop 1.0, todavía no lo he probado, si algo de este tutorial ha cambiado (especialmente la nota anterior), editare el post en cuanto lo pruebe.

En definitiva, monodevelop para mí es un IDE que estoy empezando a tener en cuenta, yo estoy ahora mismo como programador PHP4(sin objetos)+MYSQL y no uso editores WYSIWYG para nada, utilizo eclipse+aptana si el pc en el que trabajo tiene bastante memoria y uso quanta en el curro que es un PIII justito. Cuando ya tienes experiencia con html+ css+ javascript + librerias ajax(jquery,mootols …) perfectamente puedes prescindir de este tipo de editores y tirar de F5 de firefox. No me convenze la POO de PHP5 y por eso veo mejor opción ASP.NET con C#, por la forma más correcta de tratar los objetos. Ante todo me gusta el software libre, por eso empeze con php, … pero aunque .NET heche atras por ser de M******** parece que se ha estandarizado, eso ha ayudado a la implementación que ha hecho la gente de Miguel Icaza. El problema es que pienso que en unos años ni M******** NO respetará su propio estandar con tal de marcar una diferencia de mercado. Hoy os digo que tengo intención es de aprender .NET profesionalmente asi que igual os digo mañana que no vale la pena por que todavía no se ha estabilizado como lenguaje.