Documente Academic
Documente Profesional
Documente Cultură
es un curso de programacin dedicado obviamente a la Wii. La idea es que una persona con conocimientos de C mas
nos vago, pueda programar para dicha consola, facilitando en lo posible la entrada a este mundillo, con un poco de
bis entrado aqu pensando que se os va a ensear a programar para Wii, os equivocas. Sois vosotros los que tenis
acer el esfuerzo de aprender y este tutorial solo puede allanaros el camino en lo posible. Puede que os rindis a las
ras de cambio u os animes mucho al principio, pero luego dejis el tema apartado debido a que la programacin no camino de rosas o mejor dicho, es como una rosa con espinas, donde te vas pinchando todo el rato con el tallo acceder a la flor.
os programadores opinan que para hacer determinadas tareas, C no es el lenguaje mas conveniente y que no
n pegas en aprender diferentes lenguajes de programacin si hace falta, pero sin embargo ponen muchas pegas a
der cosas de una determinada maquina que es muy diferente a lo que ellos suelen estar acostumbrados.
e un punto de vista prctico, todo lo que se os pueda ofrecer aqu seguramente no lo podis utilizar
sionalmente de una forma directa, pero no os olvidis que la programacin es un proceso creativo y que requiere
uena capacidad de adaptacin. Por tanto qu mejor aprendizaje que tratar con maquinas como las consolas, que
nen de un hardware relativamente limitado y tan diferente con los ordenadores de hoy da?, divertirse mientras
demos y hacemos algo diferente?. No tiene precio e incluso si eres un programador profesional, a lo mejor te sirve
recuperar aquello que os motiv a elegir esta profesin y que habis perdido con la rutina.
ea del tutorial es proporcionaros una base para que podis entreteneros haciendo programas en Wii, pero el uso que
mero que debis hacer es descargaros DevkitPro. Descargaros la version 1.4.7 del instalador:
tp://sourceforge.net/project/showfiles.php?group_id=114505&package_id=160396
tp://wiki.devkitpro.org/index.php/Getting_Started
talador est desactualizado (y el curso tambien, por culpa de libfat) y os crear conflictos. Mira ste hilo que te
tp://www.elotrolado.net/viewtopic.php?f=165&t=1257837&p=1716884329#p1716884329
Linux marcan os puede echar una mano y seguramente otros usuarios del foro.
ez instalado DevkitPro, tendris un entorno basado en MinGW que os permitir programar para diversas consolas y
is usar el famoso "make" (luego se explica algo ms sobre este tema). Adems, se incluye una utilidad por si
itis un editor, llamada Programmer Notepad o algo similar, que tal vez os resulte til. necesitas los parches de Hermes:
tp://mods.elotrolado.net/~hermes/curso_wii/devkitPro_Wii_by_Hermes.rar
debeis copiarlo para sustituir las libreras y los ficheros de cabecera de libogc donde tengais instalado DevkitPro,
scribiendo todo lo que sobre escriba. Se tratan de las libreras ya compiladas con lo ultimo del git de hackmii y que
tp://mods.elotrolado.net/~hermes/curso_wii/hermes_examples.rar
o lo podis instalar en devkitPro/examples/wii/hermes por ejemplo, para que tengis una referencia.
os entenderis bastantes cosas vindolas, porque no hay mejor maestro que disponer de un cdigo que muestre se hacen las cosas. Ah se incluye los fuentes de la screenlib, que es la base grfica que vamos a utilizar en este
o, ejemplos que muestran el uso de las fuentes de letra o el ultimo ejemplo que muestra como simular una batera
erto, en este ultimo ejemplo, quiz os preguntis cmo diablos se controlan los acelermetros?, si sacas los
es a pantalla, puedes ver que si pones el mando en Vertical hacia arriba, te da unos valores distintos, que si los y hay cosas que se sobreentienden como "gforce" (fuerza G). La experimentacin es algo que vais a tener que usar
orma.
emplo, preparando este ejemplo, Hermes se dio cuenta de que hay un fallo en la librera que hace que no se
an usar los valores gforce u orientacin (orient) del Nunchuk, si no nicamente, los valores de aceleracin en RAW.
timo y en el apartado de las descargas, quiz os interese contar con: SpriteGen: editor de Sprites y mapas de Sprites: es una utilidad de Windows pero funciona desde Wine. Creador de fuentes: convierte fuentes ttf tratando de adaptar el tamao para exportalas al formato que usa la screenlib. Audacity: se usa a menudo para exportar y preparar efectos de sonido a formato RAW 8 bit con signo, con la ASNDLIB.
o de los ejemplos que Hermes ha subido, ha incluido un par de utilidades: filetochar que es una potente utilidad
onvertir archivos binarios a C usando varios tipos de variables y aadiendo alineacin, etc. y Wiiload, la utilidad
lemento del HBC para poder subir los ejecutables mediante WIFI.
in tenis un directorio de recursos con sonidos y algn grfico (mirar resources). Hay un ejecutable .bat que os
nes de plataformas MSDOS/Windows, sabrs lo que es un .bat. Pues el Makefile es como uno de esos viejos
vos de procesamiento por lotes, pero mas a lo bestia, por as decirlo. Lo que aqu tienes que saber, es que dispones
a serie de plantillas de ejemplo y no importa que no conozcas todos los detalles, solo importa que sepas lo
efecto los Makefiles que vienen en DevkitPro, tratan de compilar todos los archivos C que estn dentro del
rectorio "source" y el ejecutable toma el nombre del directorio en el que se encuentra el Makefile.
es, si tenemos un directorio llamado examples y dentro de el, el Makefile y un subdirectorio source conteniendo
tos archivos C, si desde linea de comandos hicieramos "make" el resultado sera que se compilaran todos los
vos dentro de source y obtendriamos un archivo examples.dol otro llamado examples.elf (que no deberamos
r en la release) y nos creara un subdirectorio build conteniendo los objetos de dicha compilacin. objetos dentro de build se utilizan para compilar rpidamente, evitando recompilar los archivos que no hemos
ado. Por lo tanto, si eliminamos el subdirectorio build podremos recompilar limpiamente todo de nuevo. A veces es
mbargo, en los Makefile suele existir una orden especifica que nos permite limpiar de objetos de una forma rpida.
en linea de comandos ponemos "make clean" se ejecutar una seccin de cdigo (si existe) que tratar de borrar
esos objetos.
ues:
-> se utiliza para compilar -> se utiliza para borrar el resultado de las compilaciones
orma de acelerar esto desde Windows, sera crear un fichero bat como los que se usa en los ejemplos:
keIt.bat
make pause
use" se utiliza para que la ventana no se cierre automticamente y quede a la espera de que se pulse una tecla.
general, es mejor idea utilizar un IDE que sea capaz de hacer "make" y capturar el resultado a una ventana del
ama. Esto suele tener la ventaja aadida de que si se producen muchos errores, bajo linea de comandos se suele
r parte del listado y que algunos IDE permiten ir directamente a la linea causante del problema pinchando en el
INTERIORIDADES DE UN MAKEFILE
is algunos de los ejemplos, quiz habris notado que los Makefile compilan solo los ficheros que se necesitan y
ncluso se permite compilar ficheros que estn fuera del subdirectorio source tomndolos desde el
orio resources.
mo se consigue eso?. Pues, si miramos en las "tripas" del Makefile, vemos cosas como:
TARGET := $(notdir $(CURDIR)) BUILD := build SOURCES := source RESOURCES := ../../resources DATA := data INCLUDES := include ../resources
son como las variables de entorno que se pueden usar en Windows y en este caso estn indicando: TARGET: este es el nombre del fichero que queremos obtener, el codigo "raro" que le sigue, le est diciendo que pille el nombre de actual directorio pero sin ruta. Esa es la razn de que si se compila example1, obtengmos un example1.dol. BUILD y SOURCES: son la razn de que se compile lo que hay dentro del subdirectoriosource y que los objetos se almacenen en el subdirectorio build. RESOURCES: es una variable que se crea para poder compilar los fuentes externos. El .. implica subir un directorio y dado que al coger los fuentes, el compilador estar en el directorio source ../../ nos sita fuera del directorio donde estamos compilando y ../../resources nos mete dentro del directorio resources de los ejemplos. DATA: por lo general devkitPro utiliza este subdirectorio (que deberiais crear vosotros) para tratar con ciertos tipos de datos. INCLUDE: esta variable os permite especificar varios destinos donde encontrar ficheros .h necesarios para vuestra aplicacin. En este caso, sealan a un hipottico subdirectorio include y a ../resources, para que encuentre los ficheros .h de los datos que tenemos enresources.
LIBS := ../../tremor/libtremor.a -lfat -lasnd -lmodplay -lwiiuse lscreen -lbte -lz -logc -lm
ara referencia a la libreria libtremor.a que se encuentra en el directorio "tremor" que es "hermano" del directorio de
talle importante: El orden de colocacin de las libreras, es importante. Hay libreras que usan funciones que
den de otras libreras y que si no estn correctamente ordenadas, se produce un error. La forma de ordenarla, es la
decir, que para construir nuestro ejecutable, libm.a (-lm) se utiliza antes que libtremor.a.
hay varios detalles importantes. Dentro del Makefile, el smbolo # se utiliza para comentar, por lo que el resto de
ea es ignorado. Si echis un ojo a esa linea "rara" se ve que es la responsable de que se compilaran todos los
gar de eso, se ha reemplazado por los ficheros a compilar. El primero es main.c que est dentro de source y el
ndo emplea el contenido de la variable RESOURCES para componer la ruta donde encontrar "econmy.c" que es el
o MOD que usa el primero de los ejemplos que se han pasado. veis, la forma de usar el contenido de la variable RESOURCES es $(RESOURCES).
se quiere aadir mas ficheros .c que es lo que se tiene que hacer?. Pues aadirlos a la lnea, como estis viendo.
orma de trabajar es conveniente, por que por ejemplo, si se usara la forma "automatica" que utilizan los ejemplos
vkitPro, aadira todos los ficheros C presente en RESOURCES, cuando solo necesitamos uno.
o, hemos visto cosas bastante importantes, pero como puedo hacer que el Makefile interprete otras ordenes?. Por
plo, en algunos de los programas se utiliza el Makefile para ejecutar el programa haciendo "Make run".
oad necesita una variable de entorno llamada WIILOAD con la IP de la Wii para poder pasar el ejecutable al HBC, al
r la palabra "export" el make se ocupar de ello, haciendo visible esa variable para todos los programas.
run:
wiiload $(OUTPUT).dol
cir: que el Makefile tiene la posibilidad de ejecutar "make run" pero necesitis dos cosas: aadir la variable de
no WIILOAD tal y como se explica (cambiando la IP por la de vuestra Wii obviamente) y por otro lado, teneis que
r wiiload.exe en alguna ruta sealada por el PATH. Un buen sitio sera C:\devkitPro\msys\bin
in teniendo en cuenta que est en la carpeta "util" que es un directorio "hermano" a la aplicacin, podrais utilizar
rmula:
s ejemplos, se usa ejecutar.bat que directamente, crea la variable de entorno y pasa un parmetro para evitar que
licaciones, al no ver ningn parmetro, interpreten que no se puede volver al cargador y al hacer reset, se vuelva
n del sistema.
sotros quereis aadir otros usos. pues por ejemplo, podes aadir:
e que [tab] representa al pulsar el tabulador para separar el comando. Luego la cosa sera hacer "make convertir"
ramar en C, no es programar en BASIC". Suena de perogrullo, pero esto tiene sus implicaciones.
mero que debes saber sobre Wii, es que utiliza "Big Endian" para almacenar los datos. Los procesadores Intel usan
ene que ver con la forma de almacenar el dato. Cuando un dato ocupa mas de un byte, existen dos formas de
izarlo de mayor a menor peso, que eso se conoce como Big Endian y de menor a mayor peso, que se conoce como
Endian.
ataformas Intel se utiliza la ordenacin de menor a mayor (Little), asi por ejemplo, para almacenar en memoria el
256 en decimal, el primer byte sera 0 y el segundo 1 (1*256+0= 256). Sin embargo, en la Wii sera al reves,
ro el 1 y despues el 0.
n lo cual si por ejemplo, quieres importar un BMP, o un WAV, pues te toca cambiar el orden de los bytes de los
s una de las razones por las que es util exportar los datos a C en RAW, puesto que si importas una lista de numeros bits, no se ve afectada por la forma de almacenar el numero en memoria (es decir, 32767 es el mismo numero en
s sistemas, pero cambia la forma en que ese numero se almacena en memoria o dentro de un fichero, por
plo).
pre que importes datos desde una plataforma Intel, tendrs que tener en cuenta el tipo de Endian que usan los
unsigned short inline swap_16(unsigned short a) { // para numeros de 16 bits return( (a<<8) | (a>>8)); } unsigned inline swap_32(unsigned a) { // para numeros de 32 bits return( (a<<24) | (a>>24) | ((a<<8) & 0xff0000) | ((a>>8) & 0xff00)); }
LOS TIPOS DE DATOS
i se usan los tipos de siempre, pero ademas aade otros tipos que son abreviaciones, de forma usual:
char
s8
unsigned char
u8
short
s16
unsigned short
u16
int
s32
unsigned int
u32
long
s3
unsigned long
u32
entero de 64 bits con signo: rango -0x8000000000000000 a long long s64 0x7fffffffffffffff
u64
float
f32
double
f64
ien podeis ver formas como vu16 que se refieren a un tipo "volatile" y otros tipos predefinidos en
tPro/libogc/include/gctypes.h.
es algo que tenis que, necesariamente, tener en cuenta en maquinas como Wii y es que su procesador, adems de
jar los tipos que he listado arriba, necesita almacenarlos en memoria alineados con respecto a su tamao.
decir, si se esta manejando un int el procesador solo puede leer ints o almacenar ints en direcciones alineadas a 4
: 0, 4, 8, 12, 16.....
ataforma Intel estas acostumbrado a que un int, lo puedes guardar en direcciones desalineadas. y esto es algo que
structura no ser igual compilando en Windows que en Wii, por qu?. Pues porque en Windows la alineacin del
no es problema, por lo que "lie" ocupar un byte y "repido" 4 bytes. Sin embargo, en Wii el compilador adems de
aadir 3 bytes de padding despues de "lie" para que asegurar que "repido" est alineado a 4 bytes.
es algo que tendris que tener en cuenta y usar inteligentemente las estructuras y gestionar las variables de distinto
o adecuadamente: acostumbrarse a alinear los datos y paddearlos de forma adecuada, puede redundar en una
sucede si el procesador lee un dato de forma desalineada al tamao del dato?. Pues que os volveris locos para
ma es que el procesador trabaja sobre memoria cacheada normalmente, mientras que las DMA (perifricos que uso) no. Por lo tanto, debemos tener en cuenta dos cosas: que los perifricos suelen acceder a memoria usando
eterminada alineacin, al igual que pasa con los datos normales y que existe un mecanismo que se encarga de
neacin utilizada es de 32 bytes. Para obtener memoria con esa alineacin, podemos recurrir a usar memalign(32,
lugar de malloc(x) para asignar memoria, pero tambien podemos declarar una variable global de forma que tenga
ineacin:
u8 buffer[1024] __attribute__ ((aligned (32))); u8 buffer[1024] __attribute__ ((aligned (32)))={0}; // si quereis fijar algun dato.
neas de cache usan 32 bytes, por lo tanto, es conveniente alnear el dato a 32 y asi mismo asegurar el padding
s de relleno):
#define MAX_BYTES 17 u8 buffer[MAX_BYTES+32] __attribute__ ((aligned (32))); // forma rapida de aplicar un padding de seguridad
emplo, si quierees subir una textura o un sonido con ASNDLIB, es preciso que la memoria pasada est alineada
amental) y con el correspondiente padding (conveniente). Si no se tienen en cuenta esas reglas, es posible que el
EL MECANISMO DE LA CACHE
en una serie de funciones encargadas del refresco de la cache y de su escritura en memoria. Prestad atencin
e no comprender el uso de estas funciones, suele acarrear muchos problemas. Dentro de screenlib o de la
LIB, no precisis utilizarlas, pues ya se usan de forma automtica, pero os ser til para otras cosas: DCFlushRange(void *startaddress,u32 len);: esta funcin recibe la direccin de memoria (debera estar alineada a 32) y la longitud en bytes (debera estar paddeada, pero ya lo hace ella) de la memoria que queremos "flushear". Es decir, lo que hace es escribir los datos desde la cach a la memoria, si en la cache hay cambios que no se han reflejado en la memoria en todo ese rango. Por eso es til llamarla antes de hacer una DMA. DCInvalidateRange(void *startaddress,u32 len);: esta funcin recibe la direccin de memoria (debera estar alineada a 32) y la longitud en bytes (debera estar paddeada, pero ya lo hace ella) de la memoria que queremos "invalidar" en la cach. Es decir, lo que hace es leer desde la memoria todo ese rango, perdiendo todos los datos almacenados en la cach. Resulta til si un perifrico nos suministra datos en memoria mediante DMA, para que la cach se refresque y al leer desde el procesador, obtengamos los datos correctos.
xcepciones son un fastidio, cada vez que el programa la casca, de donde viene el problema?.
libreras se han pasado, tenis una modificacin particular de Hermes del cdigo encargado de gestionar
ciones. En lugar de pegar el pantallazo y quedarse muerto en espera de que pulses un boton del PAD de
ambin tiene otro truco, existe una variable de cadena que podeis cambiar de forma externa, para que os muestre
ntro de funcin se produce una excepcin, se mostrar la cadena "funcion()" y sabris que la excepcin ha ocurrido
e punto.
in existe un truco para conocer exactamente, en que funcin ha ocurrido la excepcin. Con debug_str tal vez
as saber en que funcin global haya ocurrido, pero de forma imprecisa. Mas bien os puede servir para acorralar el
ema, si es reiterativo, pero existe otro truco: cuando sale la pantalla de excepcin, aparece un listado de los
ros y justo abajo, aparece una serie de direcciones de memoria que son la traza de donde ocurri la excepcin.
es bien, existe una utilidad que permite a partir de la informacin de debug que proporciona el elf, conocer a que
La screenlib (prolegmenos)
o, toca meterse en materia directa, as que primero nos alejaremos un poco para ver las cosas en perspectiva.
rera grfica, como todas, se basa en las GX (podis descargar un interesante documento sobre el tema aqu).
ra consola utiliza un mecanismo para pasarle los datos al GX, llamado FIFO (First In, First Out) que tiene como una
s cualidades que permite pasar datos desalineados con su tamao (es decir, aqu es posible enviarle un int sin que
alineado a 4 con la memoria empleada en el FIFO) y que en definitiva, marca la capacidad total de instrucciones/
mao empleado por defecto en screenlib es fijado desde FIFO_SIZE a 1MB exacto. Evidentemente, es posible usar
tamaos mayores si es necesario, pero 1MB es un tamao mas que suficiente para muchos usos e incluso se puede
car un salto para que el GX utilice otra memoria fuera del FIFO para desarrollar sus tareas (mi programa Guitarfun esa tcnica).
bueno, no vamos a perdernos en divagaciones, basta con saber que en cada frame de vdeo, disponemos de un
de 1MB para enviar datos al GX, pero que el GX va machacando instrucciones al mismo tiempo, por lo que va
do espacio libre para nuevas instrucciones / datos (trabaja en anillo) y que posiblemente, es algo de lo que no te
s que preocupar.
ro lado, conviene que sepis que las GX utilizan por hardware dos matrices de transformacin de polgonos: una de
ccin y otra Mundial. En screenlib la Mundial no se utiliza (se le pasa una matriz unitaria, que no provoca cambios)
e Proyeccin es fijada para usar el mtodo Ortogrfico que es mas adecuado para usos 2D.
cha matriz se establece un rango X/Y ajustado a la resolucin de pantalla y con un ligero reajuste en el caso de
bles SCR_WIDTH y SCR_HEIGHT. Si la resolucin es <=480 es obvio que se trata de una seal progresiva o que refrescan a 60Hz (aqui la resolucin sera de 640x480). En modo PAL la resolucin es de 640x528 y el refresco
Hz.
o de Z, como ya se ha comentado utiliza un rango de 1000. De forma interna, el rango sera de 0 a -999, siendo ese
el punto mas alejado. En la librera puesto que tratamos con 2D, se utiliza el trmino de layer (capa) puesto que en suelen utilizar varias capas de grficos y para hacer mas amigable el tema, se niega el valor para hacer que el sea de 0 a 999 y evitar el uso de nmeros negativos. Asi pues, podemos hacer uso de 1000 capas y la capa 999 se
a considerar como el fondo de pantalla, mientras que layer= 0 es poner los graficos en primer plano. Al utilizar una
ccin Ortogrfica, no se produce un escalado con la distancia para disminuir el tamao, tenedlo en cuenta.
TRANSPARENCIAS Y TRANSLUCIDEZ
enlib es inicializada para utilizar colores con Alfa por debajo de 8 (en un rango de 0 a 255) como colores
parentes, tanto en texturas como en color directo a los polgonos. Esto significa que no se debe hacer uso de los
s de color que no empleen Alpha (modo 16bits RGB565) dado que dichos colores no sern dibujados en pantalla.
as a esto podemos dibujar Sprites sin que su color de fondo, se superponga al fondo (evidentemente, siempre que
or usado de fondo, sea considerado transparente). Si utilizis el programa Spritegen y queris exportar los sprites
r directo de 16 bits, en "Export to C", desmarcar "Use Palette" y en "Direct Color" selecciona 15 bits. Si queris
rtir directamente esos sprites en tiles, podis fijar la alineacin a 32 bytes ah. De esta forma exportareis los sprites RGB5A1 y si el fondo est utilizando el color 0 (que por defecto tiene Alpha=0 en Spritegen) pues ya lo tenis listo
uncionar. Solo os faltar crear la textura a partir de los datos del sprite para tenerlo funcionando.
ejemplo 3, podis ver que se ha hecho una exportacin de datos desde Spritegen a 8 bits con paleta ("Export to
arcar "Alpha Enable" y "Use Palette". "Direct Color/ Palette Color" a 15 bits). En dicho ejemplo, se asigna memoria
da para alojar los tiles de los sprites y se conserva el mapa de color original. Eso os permite no tener que alinear
tos exportados, pero la ventaja principal, es que sabiendo que el indice de color transparente es 0, podis utilizar el original para detectar colisiones de forma mas precisa: un color en el mapa original distinto a 0 representa algo
o". podis observar, si exporto color directo o con paleta a 15 bits (+ 1 de Alpha! y Wii solo admite paletas de 16
) en realidad, ese Alpha solo va a servir para determinar si un color es transparente o no (si se dibuja o no se
a, vamos): pero Wii puede usar un rango mas amplio que 16 bits (y realmente, Wii trata de forma especial los
es con el bit Alpha=0, puesto que usa un modo de color denominado RGB5A3 y por eso a RGB5A1 es conveniente
alidad, vosotros podis especificar tambin un color en formato RGBA8 (8 bits por componente) para dibujar
ficies, o cajas desde screenlib y eso os permite hacer una especie de filtro para los usos con textura o sin textura.
nslucidez es un fenmeno que consiste en poder ver objetos que estn detrs de otros objetos a travs de ellos.
or ejemplo, un cristal es un material translcido, pues permite ver lo que hay detrs aunque le cause una
erencia (por ejemplo, si ves a travs de un cristal verdoso, los objetos de detrs tomarn dicha tonalidad). La
ncia real con la transparencia es que esta ltima, realmente no dibuja los pxeles por lo que no ocupan espacio en
enacin Z (el layer). As pues, para dibujar objetos translcidos de forma correcta, debemos dibujar PRIMERO los
os que queden detrs, pues si el objeto translcido usa layer 0, la ordenacin Z no permitir que se dibuje nada
s (por ejemplo, en layer 100). Y las partes transparentes, en realidad son "agujeros" en el objeto
n un rango de 0 a 255: Alpha <8 -> Transparente, Alpha>=8 ->Nivel de translucidez, Alpha==255 -> Color Slido
La screenlib
INICIALIZAR
InitScreen();
es todo lo que tienes que hacer en tu main() para que el video est operativo usando la configuracin que tengas en
cerlo, SCR_WIDTH y SCR_HEIGHT te informar de la resolucin que estas empleando y se explic en el anterior
ulo, puedes determinar si el refresco es a 60Hz porque el alto sera de 480 pxeles.
mbargo, conviene recordar que la lbrera trabajar pensando en 4:3 y no en 16:9. En ese caso, te tocar a ti
minar el formato de la pantalla, ya que la resolucin de salida seguira siendo la misma y aplicar los factores de
a correspondiente.
quieres conocer si estas usando una configuracin de 16:9 puedes inicializar as:
usos especiales, puedes cambiar el Viewport. Por ejemplo, podras virtualizar la pantalla para tratar los 16:9 de proporcionada directamente, cambiando el ancho, pero no se aconseja porque por ejemplo, si ajustases para
ar a 854x480, realmente, estaras condensando 1,333 pxeles en 1. Para ciertos grficos quiz no sea mucho
ema, pero para letras o pequeos detalles, es mejor desproporcionar un poco a que te falte resolucin.
ejemplo 3 se muestra como utilizar un Viewport de 480x360 para "inventarme" una resolucin de la que carece Wii
ta forma:
mo se puede apreciar, se aplica una correccin para video NTSC (<=480) y espero que entiendas que esos valores
alguna razn quisieras volver a los valores originales, es tan facil como usar sta funcin:
RestoreProjection();
ejemplo 3 se puede observar como se cambia el valor de una variable: xclip. Esta variable se utiliza para limitar derecha el texto visible (funcin s_printf, que veremos mas adelante)
FINALIZANDO EL FRAME
ncin Screen_flip(); se encarga de esperar a que se dibujen todos los grficos, sincronizar con el video vertical,
ltima funcin que debes llamar para completar el frame de video y mientras no sea llamada, no podrs apreciar
mbios efectuados.
DIBUJANDO CARACTERES
rera dispone internamente, de dos sets de caracteres ANSI, los cuales son facilmente seleccionables con la
n SelectFontTexture:
SelectFontTexture(0); // -> selecciona el font por defecto SelectFontTexture(1); // -> selecciona el font extra/externo
vel Avanzado)
rera permite reemplazar el segundo juego de caracteres por otro que haya capturado el usuario utilizando la
n de Nivel avanzado)
ariables PX y PY: son las encargadas de proporcionar las coordenadas de inicio del texto (en pxeles) que
: se encarga de suministrar el color de las letras en formato RGBA8 (ten cuidado de no utilizar esta variable para
osa).
or: proporciona el color del fondo tambien en formato RGBA8. Por defecto es 0.
center: est pensada para centrar en horizontal el texto si la pones a 1. Por defecto est a 0 (desactivada).
etter: Ccontrola los diferentes tamaos de letra, pero para vosotros sera mejor usar la funcin letter_size().
r_size(tamx, tamy);: ajusta el tamao de las letras a tamx, tamy. El font original es a 16x32 pero podis
(avanzado): esta variable controla el lmite derecho de las letras para su visualizacin. Si un caracter supera ese
, el resto del texto ser ignorado, pero nota que esto no equivale a hacer un recorte exacto en ese punto y si un
ter excede por ancho ese lmite, ser totalmente visible si es posible
ntf( char *format, ...);: funciona de forma similar a la funcin estndar printf, que como es de sobra conocida,
no necesita mucha explicacin. La nica diferencia, es que si un texto se sale de la pantalla, no se proceder a un
o de lnea automtico, cosa lgica si pensais que aqu el tamao de letra es variable. Por cierto, las letras desde
se escriben en layer 0.
_str(u32 x, u32 y, u32 z, u32 color, char *string,u16 tamx, u16 tamy);: si lo que queris es imprimir una
e cadena, sin formato, etc, podis utilizar sta funcin que como podis observar, necesita de coordenadas x,y,z (la si se aleja en sentido negativo), color (para el fondo usa bkcolor), la cadena de texto y el ancho y alto de los
teres.
La screenlib - Texturas
exturas son un asunto que merecen un captulo aparte dentro de sta documentacin. La screenlib ofrece soporte
exturas con paleta (soportando las 16 paletas que provee las GX) o sin paleta y tambin para modos de color que
n directamente soportados por las GX, pero con una pequea adaptacin son posibles. Antes de nada, tienes que que Wii aloja sus texturas en la memoria "normal", es decir, en otras mquinas tienes que subirlas a la VRAM,
ras que aqu bastar con que esa memoria est alineada con 32 (recuerda memalign, que lo mencionamos al
pio) y que hayamos flusheado los datos, puesto que la textura se trabaja fuera de cach por las GX. Como
eenlib no pretende ser un sustituto de las GX, si no un complemento de ellas, para trabajar, necesitars usar los
GXTexObj text; // -> esta es la forma de definir un objeto de textura GXTlutObj palette; //-> esta es la forma de definir un objeto de paleta
exturas se organizan internamente en tiles, generalmente de 4x4, pero hay otras organizaciones de datos. Por
ucin, sera conveniente que tanto el ancho, como el alto de la textura, fuera divisible entre 8 para estar seguro de
uestra textura no parte tiles (que yo no recuerdo el lmite exacto y en todo caso, es un margen mucho mas
mensiones mximas a usar en textura son de 1024x1024, lo que implica un tamao empleado de 1,2 o 4 MB (asi te parece pequeo, ya ves que no lo es tanto).
CREANDO PALETAS
namente, screenlib utiliza una funcin llamada ctlut() para crear una paleta partiendo de los datos proporcionados
ma externa.
mato de las paletas es de 16 bits y para trabajar con la screenlib deberias usar modos TLUT_RGB5A3,
_RGB5A1, TLUT_SRGB5A1:
#define Cambia el orden de los bytes del color (util para importaciones) TLUT_LITTLE_ENDIAN 128
Para RGB5A3 (si el bit Alpha de mas peso, es 1, el color es RGB5A1, en otro #define TLUT_RGB5A3 2 caso RGB4A3), Azul menor peso
Pseudo RGB5A1 (es RGB5A3, pero asumiendo que si Alpha==0,color==0) #define TLUT_RGB5A1 2 ,Azul menor peso
#define TLUT_RGB565 3
#define TLUT_SRGB565 4
Para RGB5A1 con R y B intercambiado, Rojo menor peso (al estilo de #define TLUT_SRGB5A1 5 SpriteGen)
emoria de la paleta debe estar alineada a 32 bytes y ser de tipo u16 (unsigned short). Si quieres asignar un color
leta manualmente, partiendo desde un color RGBA8 (con Rojo como menor peso) puedes usar Color5551(x) para
void CreatePalette(GXTlutObj *palette, int format, int pal_index, u16 *mem, int entries);
e: palette: puntero a objeto de paleta. format: formato de color (TLUT_RGB5A3, TLUT_RGB5A1, TLUT_SRGB5A1). pal_index ndice de paleta a usar dentro de las GX (0 a 15). Tambien fija una variable para relacionar paleta y textura. mem memoria conteniendo las entradas de paleta (alineada a 32, recuerda). Recuerda que los datos originales sern modificados. entries nmero de entradas de la paleta (hasta 256, aunque fsicamente, hay paletas que soportan 4096 colores).
LA FUNCIN SETPALETTE()
alette(int pal_index); se usa para fijar una variable interna con el ndice de paleta (de 0 a 15) a utilizar cuando
mos una textura que haga uso de paletas. Lo normal es que primero creemos paleta, lo cual nos ajusta esa variable
ma automtica y luego procediramos a crear nuestras texturas, pero qu sucede si quiero hacer una serie de
os en la textura en cada frame? Pues que nos tocar decirle de alguna forma que paleta debe usar al crearse la
CREANDO TEXTURAS
void CreateTexture(GXTexObj *texture, int format, void *mem, int width, int height, int repeat);
texture: puntero objeto de textura. format: formato de color: TILE_CI4, TILE_CI8, TILE_RGB5A1, TILE_RGB5A3, TILE_SRGB5A1, TILE_RGBA8, TILE_SRGBA8. mem: memoria que contiene los datos de textura y donde se formarn los tiles (es decir, que se modifican los datos). Alineada a 32 bytes. width: ancho de la textura. height: alto de la textura. repeat: flag que ajusta si la textura se repite en forma de mosaico si se exceden las coordenadas de textura (por defecto, usa 0).
#define Cambia el orden de los bytes de color (util para las importaciones) TILE_LITTLE_ENDIAN 128
#define TILE_CI4 0
#define TILE_CI8 1
Para RGB5A3 (si el bit Alpha de mas peso, es 1, el color es RGB5A1, en otro #define TILE_RGB5A3 2 caso RGB4A3), Azul menor peso
Pseudo RGB5A1 (es RGB5A3, pero asumiendo que si Alpha==0,color==0) #define TILE_RGB5A1 2 ,Azul menor peso
#define TILE_RGB565 3
No deberias usarlo
#define TILE_SRGB565 4
No deberias usarlo
#define TILE_SRGB5A1 5
#define TILE_RGBA8 6
#define TILE_SRGBA8 7
uario avanzado)
efecto las texturas usan filtrado bilineal. Es posible cambiar a GX_NEAR (para que se vean cuadraditos si ampliamos
UseTexLOD(GX_NEAR , GX_NEAR);
a restablecer:
UseTexLOD(GX_LINEAR , GX_LINEAR);
o que es un uso relativamente raro, no he querido aadir un nuevo parmetro a la funcin de crear textura que en
n usuario avanzado)
SELECCIONANDO TEXTURA
SetTexture(GXTexObj *texture);
se puede apreciar, se le pasa un puntero al objeto de textura. Si se le pasa NULL (Valor 0), esas funciones no
en una serie de texturas predefinidas para poder usarlas como patrones de relleno:
STRIPED_PATTERN &tex_pattern[0] CROSS_PATTERN &tex_pattern[1] SQUARE_PATTERN &tex_pattern[2] MOSAIC_PATTERN &tex_pattern[3] NULL_PATTERN NULL
reenlib se dise principalmente, como un conjunto de herramientas que permitiesen disear mens de una forma
o menos sencilla, por lo que no es extrao que muchas de sus funciones se limiten a dibujar distintos tipos de cajas
bordes de dichas cajas, con el fin de embellecerlas y por otro lado, marcar la diferencia entre una caja seleccionada que no lo es.
o de la librera, no hay ninguna regla que impida hacer un uso 3D o 2D diferente y no hace falta trastear mucho en
entes de la librera para ver de que forma utiliza las GX y de que forma pueden convivir. Desde un punto de vista mas bien pseudo 2D) se ha tratado de facilitar ciertos usos especiales, pero adems estas funciones, suponen un
ejemplo de como se utilizan las cosas desde las GX y os da pistas sobre que es lo que deberais alterar si por
plo, queremos una proyeccin que supere el rango de 0 a 999 en Z o usar grficos en perspectiva y meternos de
en las 3D.
unciones para dibujar cajas rellenas (con un color o con una textura), son estas:
// dibuja una caja cuadrada rellena DrawFillBox(int x,int y, int width, int height, int layer, u32 color); // dibuja una caja con las esquinas redondeadas, rellena DrawRoundFillBox(int x,int y, int width, int height, int layer, u32 color); // dibuja una caja con los lados izq-der redondeados, rellena DrawRoundFillBox2(int x,int y, int width, int height, int layer, u32 color);
x, y: posicin de la caja en pxeles. width, height: dimensiones de la caja en pxeles. layer: capa de 0 a 999, siendo 999 la capa mas alejada y 0 la mas cercana (equivale a -Z).
color: color en formato RGBA8 (8 bits por componente y Rojo byte de menor peso).
unciones para dibujar el borde de las cajas (con un color o con una textura), son estas:
// dibuja el borde de una caja cuadrada DrawBox(int x,int y, int width, int height, int layer, int thickness, u32 color); // dibuja el borde de una caja con esquinas redondeadas DrawRoundBox(int x,int y, int width, int height, int layer, int thickness, u32 color); // dibuja el borde de una caja con los lados izq-der redondeados DrawRoundBox2(int x,int y, int width, int height, int layer, int thickness, u32 color);
x, y: posicin de la caja en pxeles. width, height: dimensiones de la caja en pxeles. layer: capa de 0 a 999, siendo 999 la capa mas alejada y 0 la mas cercana (equivale a -Z). thickness: grosor de la linea de 1 a lo que admita. color: color en formato RGBA8 (8 bits por componente y Rojo byte de menor peso).
porte de la librera se limita a dibujar Elipses y grficos tipo tarta (DrawSlice).Con la funcin Elipse se pueden
er crculos y circunferenvias. Y es ms, teniendo en cuenta que las proporciones de la pantalla pueden hacer que un
" en el Viewport no sea completamente cuadrado, resulta conveniente hacer un ajuste de los radios para que
// dibuja una elipse con su superficie rellena DrawFillEllipse(int x,int y, int rx, int ry, int layer, u32 color); // dibuja el borde de la elipse DrawEllipse(int x,int y, int rx, int ry, int layer, int thickness, u32 color);
x, y: posicin en pxeles. rx, ry: radio en pxeles para las dimensiones X e Y. layer: capa de 0 a 999, siendo 999 la capa mas alejada y 0 la mas cercana (equivale a -Z). thickness: grosor de la linea de 1 a lo que admita.
color: color en formato RGBA8 (8 bits por componente y Rojo byte de menor peso).
a dibujar arcos de una elipse (grficos tipo tarta o el uso que le querais dar. El objeto se dibujara entre el arco que
n degrees_start y degrees_end):
// dibuja el arco de una elipse con su superficie rellena DrawFillSlice(int x,int y, int rx, int ry, int layer, int degrees_start, int degrees_end, u32 color); // dibuja el borde del arco de la elipse DrawSlice(int x,int y, int rx, int ry, int layer, int thickness, int degrees_start, int degrees_end, u32 color);
x, y: posicin en pxeles. rx, ry: radio en pxeles para las dimensiones X e Y. layer: capa de 0 a 999, siendo 999 la capa mas alejada y 0 la mas cercana (equivale a -Z). thickness: grosor de la linea de 1 a lo que admita. degrees_start: angulo en grados (de 0 a 360) de inicio. degrees_end: angulo en grados (de 0 a 360) final. color: color en formato RGBA8 (8 bits por componente y Rojo byte de menor peso).
LA FUNCIN DE LINEA
ncin DrawLine() se incluy pensando ms en ciertos usos especiales que pensando en ella como la funcin que
pre deberias utilizar". La funcin est preparada para dibujar lineas de mas de un pxel de grosor y en realidad dos tringulos de forma interna. Pero tiene un pequeo defectillo, para dibujar lineas horizontales o verticales,
apoyo para poder ligar con otras lineas y formar cajas, pero si esa lnea es mas gruesa de un pxel y digamos que "rampas", la unin no ser buena. Notese tambin que le influye si tenemos una textura o no en activo (con
xture()).
eris dibujar lneas u otros polgonos de forma rpida, deberais utilizar GX_Begin, el cual se describe luego, mas
// dibuja lineas con o sin textura y de diferentes grosores DrawLine(int x,int y, int x2, int y2, int layer, int thickness, u32 color);
x, y: posicin de inicio en pxeles. x2, y2: posicin final en pxeles. layer: capa de 0 a 999, siendo 999 la capa mas alejada y 0 la mas cercana (equivale a -Z). thickness: grosor de la linea de 1 a lo que admita. color: color en formato RGBA8 (8 bits por componente y Rojo byte de menor peso).
DIBUJANDO SUPERFICIES
e hace de sta librera que resulte til para hacer juegos 2D de forma directa, es sin duda, la
n DrawSurface().
uncin permite dibujar un objeto de textura directamente, proporcionndole unas dimensiones que no tienen
e coincidir con las de la textura utilizada. Es decir, si la textura tiene un ancho de 32x32, no hay nada que impida
arla como superficie mayor o menor, lo cual lleva implcito un aumento o reduccin de tamao (de ah que nos
ues DrawSurface(), se puede utilizar tanto para dibujar una ventana en la cual utilizaremos una textura como
ficie donde dibujar pxeles (en el ejemplo 1 os incluyo una muestra de como utilizo una textura para dibujar) o para
// dibuja un objeto de textura como superficie DrawSurface(GXTexObj *surface,int x,int y, int width, int height, int layer, u32 color);
surface: puntero a objeto de textura que describe la superficie (creada conCreateTexture()). NO usar NULL aqu. x, y: posicin de la superficie en pxeles. width, height: dimensiones visuales de la superficie en pxeles. layer: capa de 0 a 999, siendo 999 la capa mas alejada y 0 la mas cercana (equivale a -Z). color: color en formato RGBA8 (8 bits por componente y Rojo byte de menor peso).
dibujar polgonos, resulta mas adecuado usar las GX directamente, puesto que as ganamos en velocidad y
ente es muy sencillo con ayuda de unas pocas funciones de apoyo que se han aadido. Un ejemplo de uso, lo
s ver en el ejemplo 2, donde se utiliza GX_Begin para dibujar las lineas remarcadas que forman las "montaas"
se hace un uso especial, partiendo de una coordenada de origen y usando esto para dibujar 4 lineas en una
mero que tenemos que hacer, es preparar lo necesario para dibujar polgonos con o sin texturas:
ConfigureForColor();
a es la funcin necesaria para ajustar el uso de vrtices para admitir coordenadas de posicin y color:
ConfigureForTexture(int scale);
a es la funcin necesaria para ajustar el uso de vrtices con coordenadas de posicin, color y coordenadas de
a scale: este parmetro se utiliza para fijar el factor de escala para la textura, debido a que las coordenadas las especificamos como enteros de 16 bits. Para entender esto, tienes que saber que para el GX, los limites de una textura se especifican como nmeros flotantes siendo 0.0f uno de los extremos y 1.0f el otro, aunque tambin es posible utilizar magnitudes negativas (en caso de crear textura con el parmetro repeat=1).
ues, ste factor de scale es un desplazamiento cuyo valor real es 1<<scale para representar 1.0f.
cir, que si fijamos scale=10, como 1<<10==1024, un valor de 0 representara a 0.0f mientras que 1024
sentara a 1.0f.
amos un ejemplo: imaginaos que creamos una textura con unas dimensiones de 64x32 texels (el texel es como el pero referido a textura). A ojos del GX, las dimensiones irian desde 0.0f a 1.0f, de forma independiente a ese
o.
es bien, si especificamos un factor de scale= 10, eso significa que si damos una coordenada de textura tx=1024( en
ho), tx sera equivalente a 1.0f pero a efectos prcticos de textura sealara a 64, teniendo en cuenta el ancho de la
a en texels.
embargo si especificamos ty=1024 (el alto), a efectos prcticos sealara a 32 que es el alto de la textura y no a
e parecer lioso pero la idea es que las coordenadas de textura no dependan exactamente del tamao de la textura,
ue obviamente, hay una relacin directa. Vosotros lo que tenis que ver es que 1<<scale representa al lmite
al y horizontal de la textura (y el otro es 0, obviamente) y no caer en el error de pensar que como el ancho de la
ra es 64 y el alto 32, hay que pasarle esos valores numricos o 1.0f y 0.5f, porque la escala hace referencia al valor
0f, ya que independientemente del tamao real de la textura, 1.0f es el limite derecho, pero tambin el inferior.
ues, si la textura es un rectngulo, las coordenadas quedaran as, para un factor escala de 10:
o que 1.0f en realidad, rebasa el lmite de la textura, lo mejor es quedarse un poco corto, de forma que sera mejor
ero que con este ejemplo, hayis captado mejor lo de las coordenadas de textura y sus escalas (se puede
ificar floats como coordenada de textura directamente, pero por cuestiones de velocidad y espacio, es mejor utilizar
GX_BEGIN()
egin es la funcion que nos permite dibujar polgonos desde las GX. Para utilizarla desde screenlib debis hacerlo
GX_Begin(primitive,
GX_VTXFMT0, vertices);
primitive: puede ser GX_POINTS, GX_LINES, GX_LINESTRIP, GX_TRIANGLES, GX_TRIANGLESTRIP, GX_TRIANGLEFAN, GX_QUADS. Si quieres saber ms, en la documentacin sobre las GX, se describe este tema, pero se hace un resumen aqu:
GX_POINTS
GX_LINES
Dibuja lineas, necesita dos vrtices para la primera linea, pero el resto de lineas utilizan el GX_LINESTRIP vrtice de la ltima linea dibujada, mas uno nuevo. Es decir, que para dibujar dos lineas se necesitaran 3 vrtices y las dos lineas compartiran uno de ellos
GX_TRIANGLES
Dibuja tringulos enlazados de forma que el primero requiere tres vrtices, pero los siguientes comparten dos vrtices con el ltimo tringulo dibujado mas uno nuevo. Resulta GX_TRIANGLEST adecuado para crear objetos en forma de tiras o anillos (objetos torno, por ejemplo).Por RIP ejemplo, dos trianglestrips requieren 4 vrtices, mientras que tres trianglestrips requieren 5 vertices
GX_TRIANGLEFA N
Dibuja tringulos utilizando un vrtice como referencia central (el primer vrtice especificado), el ultimo del anterior tringulo mas uno nuevo. Obviamente, el primer tringulo
precisar de trs vertices. Por ejemplo, dos trianglefan requieren 4 vertices, mientras que tres trianglefan requieren 5 vertices. Resulta adecuado para hacer graficos de tarta, cerrar figuras, para modelar una sombrilla...
Los quads son figuras que requieren 4 vrtices. Los vrtices requiere usar un orden horario o antihorario (es decir izq-arr, der-arr, der-abj, izq-abj por ejemplo) y se debe evitar vrtices GX_QUADS cruzados o que puedan generar dos planos. Son tiles para dibujar cuadrados/rectangulos y desde un punto de vista 3D, paredes, techos, suelos...
GX_VTXFMT0: esto especifica que vamos a usar el formato de vrtices 0, que es el que programamos con ConfigureForColor() y ConfigureForTexture(). vertices: numero de vrtices a usar, hasta un lmite de 65535 (quiz el 0 represente 65536, pero vamos, que puedes usar un buen puado de una sola llamada). Y por supuesto, siempre que no superes el tamao del FIFO. Recuerda que debes darle ls vertices exactos (o se producir un cuelgue de la muerte)
reen.h tienes dos funciones definidas para pasar los parmetros necesarios a los vrtices:
// para aadir vrtices con posicin y color AddColorVertex(s16 x, s16 y, s16 z, unsigned color); // para aadir vrtices con posicin, color y coordenada de textura AddTextureVertex(s16 x, s16 y, s16 z, unsigned color, s16 tx, s16 ty);
ConfigureForColor(); // usa color GX_Begin(GX_LINE, GX_VTXFMT0, 2); // dibuja una linea (por tanto pasamos 2 vertices) AddColorVertex(0,0,0,0xffffffff); // primer vertice AddColorVertex(SCR_WIDTH, SCR_HEIGHT,0,0xffffffff); //segundo vertice GX_End();
ConfigureForTexture(10); // usa textura y fija escala a 10 ( 1<<10== 1024) (notese que usar siempre la textura especificada por SetTexture()) GX_Begin(GX_QUADS, GX_VTXFMT0, 4); // dibuja un QUAD (por tanto pasamos 4 vertices) AddTextureVertex(0,0,0,0xffffffff, 0, 0); // primer vertice AddTextureVertex(SCR_WIDTH, 0,0,0xffffffff, 1023, 0); // segundo vertice AddTextureVertex(SCR_WIDTH, SCR_HEIGHT,0,0xffffffff, 1023,1023); // tercer vertice AddTextureVertex(0, SCR_HEIGHT,0,0xffffffff,0, 1023); // cuarto vertice GX_End();
sible que necesitis saltar a usar 3D o incluso que hagis un uso mas avanzado de las 2D, cambiando la matriz
ial, etc. En ese caso, tendris que restaurar el entorno para volver a trabajar con screenlib y para eso, se ha
do la funcion:
Recover2DContext();
uncin recupera en lo posible el contexto 2D de screenlib, pero si hacis usos avanzados (como cambiar la matriz
nsformacin de texturas y cosas asi), es posible que eso lo tengis que restablecer a mano. Obviamente, ser
ra responsabilidad cambiar a los distintos contextos y esta funcin no puede saber todo lo que habis cambiado y
urarlo vosotros.
do caso, dispones del cdigo fuente de la librera y algunas cosas se han organizado de forma que te sea sencillo
La ASNDLIB (Introduccin)
NDLIB es la sucesora de una librera anterior, llamada SNDLIB que fue una de las primeras aportaciones de Hermes
mebrew de Wii. Se diferencian en que la primera utiliza el DSP, lo cual permite acelerar las voces, mientras que la
nda usa el procesador de la Wii para calcular las distintas voces y adems soporta unas voces especiales pensadas
generar las voces de instrumentos, con efectos como ataque, sostenimiento y cada.
P es un procesador relativamente poco potente para la tarea encomendada. Mezclar 16 voces en estreo y con un de frecuencias que va desde un 1Hz a 144000Hz es algo que requiere bastantes multiplicaciones y operaciones
adaptar y mezclar las voces, no es tarea fcil para un procesador de 16 bits y que est pensado para trabajar de
manera (baste decir que Hermes fu el primero en darle utilidad, basndose en una informacin con 3 aos de
edad y resolviendo varias piezas del puzzle hasta hace bien poco desconocidas).
o es que por razones de capacidad de proceso, era imposible mantener 16 voces y aadir la posibilidad de que el
rabajara con los efectos de ataque, sostenimiento y cada. Se podran aadir desde fuera y probablemente,
ndo pocos recursos y algunos trucos, pero como aparte de Hermes, creo que nadie las ha usado, pues decidi
mirlas.
uestiones de proceso, no es conveniente usar muchas voces por encima de 48000Hz. La Wii trabaja a
0Hz a 16bits por sample y en estreo y la ASNDLIB permite reproducir voces Mono o Stereo, con samples de 8 o
s (con signo) y en el rango mencionado de 1 Hz (MIN_PITCH) a 144000Hz (MAX_PITCH) lo cual significa que la
a es capaz de adaptar la frecuencia de las voces para que acaben sonando a la frecuencia especificada en las voces
os 48000Hz. Sin embargo, es obvio que usar voces por encima de esa frecuencia, implica una prdida de calidad de
o y adems, multiplicar la carga de trabajo al DSP. Cuanto menor sea la frecuencia a la que se reproduce una voz,
desahogado ir el DSP, aunque es cierto que el cdigo se ha diseado para que el DSP cumpla bien con su trabajo
P se coordina con la interrupcin de audio que es llamada cada 21,333 ms aproximadamente, una vez que se
umpe la DMA de Audio y entonces se inicia un proceso de llamadas en cascada a la DMA del DSP de hasta 17 pasos,
procesar las voces. ltimo sealar en ste apartado, que las voces de sonido a usar, deben estar alineadas a 32 bytes y usar
rentemente un padding de 32 bytes. De nuevo estamos en el mismo caso que con las texturas.
La ASNDLIB - Funciones
INICIANDO
ASND_Init();
ciar la ASNDLIB, las voces estn pausadas mediante una pausa general.
PAUSA GENERAL
ASND_Pause(s32 paused);
paused: pon 1 para pausar y 0 para continuar.
ASND_Init(); ASND_Pause(0);
d en cuenta que en el momento de quitar la pausa y aunque no se oiga sonido, la librera estar trabajando y el
FINALIZANDO
ASND_End();
habis fijado en mis ejemplos, veris que sta funcin es llamada en fun_exit() que es fijada con atexit() para que
r del programa, salir de algunas libreras. Esta practica es conveniente debido a que pueden haber algunas DMAs
una vez tenemos la librera iniciada y funcionando (quitando la pausa), ahora podemos decidir que voces usar la 0 a la 15 (ya se ha comentado que dispones de 16).
ima voz que nos devolver como libre es la 0. Esta funcin se puede utilizar para conocer la primera voz sin uso,
no hay ninguna regla escrita que diga que no se pueden interrumpir voces en ejecucin en cualquier momento.
s32 ASND_SetVoice(s32 voice, s32 format, s32 pitch,s32 delay, void *snd, s32 size_snd, s32 volume_l, s32 volume_r, ASNDVoiceCallback callback); s32 ASND_SetInfiniteVoice(s32 voice, s32 format, s32 pitch,s32 delay, void *snd, s32 size_snd, s32 volume_l, s32 volume_r);
mera es el uso normal, mientras que la segunda se usa para ejecutar una voz de forma continua e ininterrumpida y
ueda por tanto, sometida al control del usuario (cambio de pitch, volumen o momento de pararla).
format: formato PCM desde VOICE_MONO_8BIT a VOICE_STEREO_16BIT (ver asndlib.h). pitch: frecuencia del pitch (en Hz). delay: tiempo de retardo en milisegundos (ms). Tiempo de espera antes de que se inicie la voz. snd: buffer con los samples a reproducir (alineado y con padding a 32 bytes). size_snd: tamao del buffer en bytes. volume_l: volumen del canal izquierdo en el rango de 0 a 255. volume_r: volumen del canal derecho en el rango de 0 a 255. callback: puede ser NULL, lo que indicara que la voz se usa nicamente para reproducir los samples especificados y despus quedar libre o la direccin de una funcin callback para trabajar a doble buffer, que ser invocada desde la interrupcin (cuidado con el gasto de CPU aqu) cada vez que se detecte que al menos uno de los buffers de voz queda libre y as asignar un nuevo buffer con ayuda de la funcin ASND_AddVoice(). En el modo doble buffer (usando callback) la voz no se libera por si misma y devuelve SND_WAITING en caso de quedarse sin samples para seguir reproduciendo. Cmo se puede observar, el modo con callback resulta mas adecuado para reproductores de audio, mientras que sin callback es mas adecuado para efectos de sonido sueltos.
tructura es la siguiente:
e voice recibe el numero de voz por si sta callback es compartida con otras voces. En el callback estis en tiempo
errupcin y cuanto menos tiempo gastis aqu, mejor. Si necesitis hacer algo pesado, despertad un hilo desde
LA FUNCIN ASND_ADDVOICE()
unciones devuelven SND_OK, SND_BUSY si no es posible aadir la voz o SND_INVALID en caso de error.
metros: voice: una desde 0 a 15 (MAX_SND_VOICES-1). snd: buffer con los samples a aadir (alineado y con padding a 32 bytes). size_snd: tamao del buffer en bytes.
cir: se puede observar que la funcin solo devolver SND_OK en caso de tratarse de una voz correcta y encontrar
e los bufferes libres para ser asignado. En ese caso, la voz ser aadida y nosotros podremos comparar y proceder
mprimiendo formato Ogg, por ejemplo. Sin embargo la librera ASNDLIBtiene un funcionamiento asncrono y
ita de mecanismos de proteccin para evitar por ejemplo, que tengamos dos buffers aadidos en la cola de
duccin (uno en ejecucin y otro en espera) y que machaquemos esos datos al descomprimir nuevos samples y
ues, se necesita algo que le diga al lector-descompresor de samples que se est quieto-parado esperando que el actual de lectura quede libre al cesar la reproduccin.
ta funcin, debemos pasarle el inicio del buffer y ese puntero ser comprobado y en caso de coincidir con el buffer
produccin o el buffer en espera, se nos devolver el valor 1, lo que indicara que deberamos esperar antes de
nar" el buffer con nuevos datos y 0 en caso de no estar en uso ese puntero.
nto antes de refrescar los datos del buffer, deberamos comprobar que ASND_TestPointer(voice, puntero)==0 y
uncin devuelve 1 si uno de los dos buffers est libre. Puesto que nosotros controlamos la secuencia de intercambio
SND_AddVoice(), es obvio que si por ejemplo, acabamos de aadir un nuevo buffer y nos queda uno libre, es que
ues, deberamos comprobar que ASND_TestVoiceBufferReady(voice)==1 antes de refrescar los datos del buffer.
e un timer de sonido global asignado a todas las voces y cuya utilidad puede ser coordinar el manejo de las voces en
// devuelve el tiempo transcurrido en milisegundos (ms) u32 ASND_GetTime(); // devuelve el numero de samples reproducidos hasta el momento en relacin a 48000Hz (48000 samples = 1 segundo)
u32 ASND_GetSampleCounter(); // devuelve el numero de samples reproducidos a 48000Hz por cada interrupcin u32 ASND_GetSamplesPerTick(); // fija el tiempo del contador global por el especificado en milisegundos (ms) void ASND_SetTime(u32 time);
os devuelve: SND_INVALID: voz no valida (fuera del rango 0 a 15). SND_UNUSED: voz sin uso. SND_WORKING: voz en uso y reproduciendo. SND_WAITING: voz en uso y esperando nuevos samples (doble buffer).
oces poseen una medida de tiempo particular que es til para determinados usos:
// devuelve el tiempo transcurrido en milisegundos, sin tener en cuenta el tiempo de delay especificado en la voz u32 ASND_GetTimerVoice(s32 voice); // devuelve el numero de samples reproducidos a 48000Hz (es independiente de la frecuencia de la voz) u32 ASND_GetTickCounterVoice(s32 voice);
te caso, no se puede fijar el tiempo de inicio de la voz, ya que de ello se encarga ASND_SetVoice().
// retorna uno si la est activa la pausa general o 0 en otro caso s32 ASND_Is_Paused(); // aade una callback de propsito general que ser llamada cada 21,333 ms (p.e: void my_globalcallback() {}) void ASND_SetCallback(void (*callback)());
CONTROL DE VOCES
// para la voz (si est en ejecucin, obviamente) s32 ASND_StopVoice(s32 voice); // si pause=1, pausa la voz, si pause=0 continua. Est pausa se anula con ASND_SetVoice() tambin s32 ASND_PauseVoice(s32 voice, s32 pause); s32 ASND_StatusVoice(s32 voice);
os devuelve: SND_INVALID: voz no valida (fuera del rango 0 a 15). SND_UNUSED: voz sin uso. SND_WORKING: voz en uso y reproduciendo. SND_WAITING: voz en uso y esperando nuevos samples (doble buffer).
ta funcin, debemos pasarle el inicio del buffer y ese puntero ser comprobado y en caso de coincidir con el buffer
produccin o el buffer en espera, se nos devolver el valor 1, lo que indicara que deberamos esperar antes de
nar" el buffer con nuevos datos y 0 en caso de no estar en uso ese puntero.
nto antes de refrescar los datos del buffer, deberamos comprobar que ASND_TestPointer(voice, puntero)==0 y
uncin devuelve 1 si uno de los dos buffers est libre. Puesto que nosotros controlamos la secuencia de intercambio
SND_AddVoice(), es obvio que si por ejemplo, acabamos de aadir un nuevo buffer y nos queda uno libre, es que
pues, deberamos comprobar que ASND_TestVoiceBufferReady(voice)==1 antes de refrescar los datos del buffer.
voces de tipo infinito o de doble buffer, puede ser til el uso de estas funciones:
// cambio dinmico de la frecuencia de reproduccin (pitch) (rango de 1Hz a 144Khz) s32 ASND_ChangePitchVoice(s32 voice, s32 pitch); // cambio dinmico del volumen (recuerda que el rango es de 0 a 255) s32 ASND_ChangeVolumeVoice(s32 voice, s32 volume_l, s32 volume_r);
A GOLPE DE NOTA
na funcin que nos permite utilizar voces para hacerlas sonar como si fueran notas de un instrumento,
rcionando la frecuencia a partir de los datos de nota y frecuencia base del sample:
entender esto, en asndlib.h tenis un definido NOTE (note, octave) que compone la nota codificada a usar en esta
cuencia base es la frecuencia a la que capturas un sonido y la nota base, es la nota que capturaste. Por ejemplo,
naos que capturis la nota Do3 de un rgano a 11025 Hz, pues obviamente, para que ASNDLIB pueda simular un
tiene que saber que relacin hay entre la frecuencia base y las distintas notas .
u32 ASND_GetDSP_PercentUse();
elve el tanto por ciento usado en un determinado instante, por si queris saber el tiempo que gasta el DSP en
sar las voces (si supera el 100%, es que al DSP le falta tiempo para procesar las voces que le estis mandando).
use.a (-lwiiuse) contiene las funciones necesarias para leer el Wiimote y sus expansiones, tales como el Nunchuk,
o Clasico o la Guitarra de GHIII. podeis observar en los ejemplos de Hermes, necesitis incluir:
#include <wiiuse/wpad.h>
e es donde se definen las funciones necesarias, aunque en wiiuse.h podeis observar tambien, que se definen ciertas
turas necesarias.
INICIAR EL WIIMOTE
WPAD_Init();
todo lo necesario para iniciar la librera, pero tambin necesitareis configurar el modo o cosas como el tiempo que
// tiempo en segundos que aguardar el mando antes de apagarse por falta de uso (p. e: 5*60 para 5 minutos) void WPAD_SetIdleTimeout(u32 seconds);
s32 WPAD_ScanPads();
uncin se encarga de leer todos los mandos mediante el mtodo polling y debe ser llamada en cada frame.
elve el nmero de PAD en activo (salvo que se produzca un error, por lo que no es del todo fiable).
es la funcin clave que deberais llamar para conocer si un mando est conectado o no y que tipo de datos devuelve.
no <0 si se produce un error, otro caso, mando conectado y datos servidos. chan: un nmero de 0 a 3 para cada uno de los wiimotes. type: puntero a una variable u32 donde podremos leer el tipo de expansin: WPAD_EXP_NONE, si no hay ninguna, WPAD_EXP_NUNCHUK, si el Nunchuk est conectado, WPAD_EXP_CLASSIC y WPAD_EXP_GUITARHERO3.
una vez que verificamos que WPAD_Probe() devuelve un valor >=0 y que conocemos el tipo, podemos pasar a leer los datos:
elve todos los botones pulsados tantos del Wiimote, como de las expansiones. En wpad.h podis encontrar las
ciones que son del tipo WPAD_BUTTON_X para los botones del wiimote, WPAD_NUNCHUK_BUTTON_X para los del
u32 buttons=WPAD_ButtonsHeld(0);
uncin nos devuelve un puntero a la estructura WPADData interna (mirad en wpad.h). De esta forma, se pueden
er a todos los datos procedentes de las expansiones, sticks analgicos o la cmara IR.
WPAD_IR(int chan, struct ir_t *ir); WPAD_Orientation(int chan, struct orient_t *orient); WPAD_GForce(int chan, struct gforce_t *gforce); WPAD_Accel(int chan, struct vec3w_t *accel); WPAD_Expansion(int chan, struct expansion_t *exp);
emplo con:
amos los datos del Wiimote 0 (el del led 1 encendido) y si tuviramos el Nunchuk conectado, podramos leer los del stick con:
// datos izquierda-derecha del stick analogico del nunchuk devuelta como u8 (128== centro aprox) wmote_datas->exp.nunchuk.js.pos.x; // datos abajo-arriba del stick analogico del nunchuk devuelta como u8 (128== centro aprox) wmote_datas->exp.nunchuk.js.pos.y; // datos correspondientes a la posicin central del stick wmote_datas->exp.nunchuk.js.center.x; wmote_datas->exp.nunchuk.js.center.y;
y mas datos como min y max, pero eso ya deberais mirarlo vosotros).
ejemplo, en caso de activar la cmara IR y ajustar la resolucin con WPAD_SetVRes() podrais leer la posicin aqu:
// posicin X de pantalla devuelta como float wmote_datas->ir.x // posicin Y de pantalla devuelta como float wmote_datas->ir.y // devuelve 1 si los datos de x e y son vlidos o 0 si no lo son (apuntando fuera de la barra sensora) wmote_datas->valid
comiendo hacer vuestras propias pruebas y buscar en wpad.h y sobre todo, en wiiuse.h la definicin de las
ntes estructuras y visualizar los datos en pantalla con s_printf(), para comprender mejor que es lo que devuelven.
, parece ser que hay algunos fallos en relacin al nunchuk y por ejemplo, el uso de los acelermetros solo funciona
w (por eso en el ejemplo 4 uso el Nunchuk desde exp.nunchuk.accel.x y no de forma similar a como uso el
ote).
EL RUMBLE
emente haciendo status=1, pondremos el motor en marcha y lo pararemos con status=0. Si queremos simular
tas vibraciones, nos tocar ir activando y desactivando el rumble con una determinada cadencia.
o se ocupa la funcin:
unciones se definen en ogc/pad.h y todo se asimila de forma interna a libogc y no necesitis incluir librera aparte.
PAD_Init();
LEYENDO LOS PADS DE GAMECUBE
u32 PAD_ScanPads();
sta funcin se leen todos los PADs conectados y devuelve una mscara que indica que mandos estn conectados o
n la versin original de pad.c, hay un fallo (que no se sabe ai se produce solo en Wii o no) que hace que si conectas
puerto 2, sin haber un mando en el puerto 1, a veces falle la conexin con ese mando 2 (Hermes se dio cuenta
rollando el emulador Wiiengine), por lo que vosotros disponis de una versin modificada que evita este fallo.
rdad la lectura usa mtodo polling y que por tanto, tendras que llamar sta funcin en cada frame,
plo de uso:
u32 npads; .... npads=PAD_ScanPads(); if(npads if(npads if(npads if(npads & & & & 1) 2) 4) 8) {} {} {} {} // // // // mando mando mando mando 1 2 3 4 conectado conectado conectado conectado
ez que conocemos los mandos que tenemos conectados, podemos usar estas funciones (mirar pad.h).
elve todos los botones pulsados. El rango va desde PAD_BUTTON_LEFT a PAD_BUTTON_START, como podeis
var en pad.h.
conocer los valores del stick (observar que utiliza un rango de -128 a 127, siendo 0 la posicin central).
conocer los valores del substick o como lo llamo yo, el pezn amarillo (observar que utiliza un rango de -128 a 127,
o 0 la posicin central).
conocer los valores analgicos de los gatillos, si se estn pulsando. Dan un rango de 0 a 255.
dad que los valores analgicos son de referencia y que en el caso de los sticks, siempre es conveniente usar una
muerta (un rango de valores que se ignora, por ejemplo, entre -32 y +32, para evitar que la posicin centrada del
sea un cachondeo y haya cierta deriva). esto ya sabis todo lo imprescindible para usar ste tipo de pads
Punto de inflexin
hasta ahora, tenis acceso al vdeo, al audio y a los distintos mandos. Tambin se han proporcionado las libreras
iladas, herramientas para crear sprites e integrar las cosas en el propio programa, de forma que Hermes ha
lido su promesa de acercaros la programacin de Wii a los que aoris los viejos ordenadores como el Spectrum y
ejemplo 3 podis observar la integracin con los sprites, incluso podis ver como leer el tiempo en milisegundos por
acin get_ms_clock() y as poder regular la velocidad de actualizacin de vuestros sprites de forma independiente
resco de pantalla (cuidando que ese tiempo sea siempre superior o igual 20ms por frame, claro). En los ejemplos se
tran como utilizar el Modplayer e incluso en el ejemplo 3, se muestra como controlar "manualmente" el modplayer
odo lo que sa ha explicado aqu y con lo que veis en los ejemplos, tenis todo lo necesario para crear juegos y
der a programarlos.
o pensar: "Eh! No nos has contado nada sobre como acceder a la SD, ni al DVD ni a los dispositivos USB. Tampoco
algunas de esas cosas me ocupar a continuacin, pero realmente lo necesitis? Es decir, plantearos si de veras, que necesitis todo eso para realizar un juego de plataformas, un juego de marcianos y cosas as. Evidentemente,
ue queris es realizar un juego donde la programacin pasa a un segundo plano y los grficos ocuparn una
da, pues es evidente que necesitareis acceder a dispositivos para cargar vuestros grficos, pero es que una de dos:
O no necesitis ste cursillo y por tanto, ya sabis muchas de estas cosas o lo tenis muy fcil de averiguar por vuestra cuenta. Este curso es principalmente, para principiantes. O no tenis muchos pjaros en la cabeza, pues todava no habis empezado a gatear y ya queris correr.
e se quiere que te des cuenta es que con la informacin que tenis hasta ahora, los ejemplos y esas herramientas que disponis, ya podis incluso echar a correr y que si ste cursillo se parara aqu, con todo lo que tenis, sera
a integrarla en tus programas, basta con aadir -lfat como librera en el Makefile (los ejemplos de Hermes, la
do los ordenadores iniciaron su andadura, se hizo necesario emplear un estndar para poder manejar texto e
ambiar la informacin y el estndar elegido fue el ASCII que se defini entre 1963-1966 y posteriormente, se
ni en 1986 dando lugar a la especificacin ANSI que conocemos actualmente. se compona de 128 caracteres, usando 7 bits por tanto, donde se le aada un ltimo bit como bit de paridad en
os 128 caracteres, los primeros 33 se utilizaban para cdigos de control no imprimibles. As por ejemplo, chr 8
senta un espacio atrs, chr 9 el tabulador, chr 10 avance de lnea , chr 13 el retorno de carro y chr 32 el espacio.
s imprimibles, el caracter '0' es chr 48, la 'A' es chr 65, y la 'a' chr 97.
gunos sistemas (LINUX/UNIX) el carcter 13 se toma como avance de lnea y retorno de carro de forma simultanea
es la razn por las que algunas veces, si abrimos un README desde el Wordpad de Windows, el texto se apelotona
mbargo, pronto se hizo necesario ampliar el set de caracteres, dado que ASCII recoga los caracteres latinos USA
por ejemplo, no recoga la , ni los caracteres de acentuacin que nosotros conocemos. Esto se recoge cmo una
sin que ocupa los caracteres desde el 128 al 255 (usando lo que antes era un bit de paridad) conocido como
dar ISO-8859-1 y as en solo 8 bits, se recogan todos los caracteres especiales de origen latino.
nlib utiliza una captura de caracteres que contiene 224 caracteres (elimina los 32 primeros caracteres ASCII, pero
rva el carcter espacio) y que recogen esa especificacin ANSI con los caracteres extendidos ISO-8859-1.
o que estos estndares solo recogen caracteres latinos, se hizo necesario crear otros estndares que recogieran
tipos de caracteres para soportar otros idiomas, como por ejemplo, el ruso y as naci el Unicode. Dentro del
de existe una especificacin llamada Utf-8 (8-bit Unicode Transformation Format) que utiliza un sistema de longitud
ble para que partiendo de 8 bits, se puedan codificar todos los caracteres Unicode.
a especificacin es la que utiliza la librera libfat a la hora de trabajar con nombres de ficheros, pero omo nos
?.
bien, Utf-8 utiliza el bit de mayor peso, el que actualmente recoge los caracteres ISO-8859-1 para cambiar y
car la tabla de caracteres a utilizar. Y al ser de longitud variable, un carcter puede ocupar de 1 a 4 bytes.
icode, los primeros 256 caracteres son los mismos que los especificados en la ISO-8859, por lo que los caracteres
del 0 al 127, corresponderan directamente a lo que nosotros conocemos. Lo que cambia es la forma de tratar el bit
ayor peso y eso compromete los nombres de los ficheros que utilicen acentos, etc.
ue tenis dos opciones: O bien procuris utilizar nombres de ficheros/directorios que no usen caracteres fuera del rango de 128 caracteres definido por el estandar ANSI. O si no os queda ms remedio, tendris que convertir de Utf-8 a ISO-8859 para imprimir o de ISO-8859 a Utf-8 para manejar nombres de ficheros/directorios que usen esos caracteres especiales.
emplo en la aplicacin Wiireader, slo interesa ver el nombre de los ficheros correctamente al visualizarlos en
lla, dado que al listar ficheros desde libfat, se obtienen en Utf-8 y por tanto, al abrirlos se conservar ese Utf-8.
ues, como sabemos que los caracteres del 0 a 127 en Utf-8 son iguales y ocupan solo un byte, solo nos restara como se codifican los caracteres del 128 al 255 para poder imprimirlos correctamente desde la screenlib.
http://es.wikipedia.org/wiki/UTF-8 podemos ver que para cubrir el rango de caracteres 0x80 a 0x7ff (nosotros solo
rango: 000080 - 0007FF Unicode (UTF-16) 00000xxx xxxxxxxx Utf-8: 110xxxxx 10xxxxxx
ues, podramos hacer una rutina de conversin de Utf-8 a ISO-8859 de esta forma:
void UTF8_To_ISO8859(u8 *src, *u8 dst) { while(1) { if(src[0]==0) {*dst=0;break;} // fin de cadena if((src[0] & 128)==0) { // rango de 0x0 a 0x7f *dst++=*src++; } else if((scr[0] & 224) ==192 && (scr[1] & 192)==128) { // rango 0x80 a 0x7ff
if(src[0] & 0x1c) { // error fuera del rango 0x80 a 0xff: caracteres no soportados *dst=0;break; } *dst++= (((src[0] & 3)<<6) | (src[1] & 63)); src+=2; } else { // error fuera del rango de 0x0 a 0x7ff o no usa especificacin UTF-8 *dst=0;break; } } }
sta rutina (que por cierto, no ha sido testeada) podris convertir cadena utf-8 como fuente, a otra ISO como
no, para visualizarla con s_printf por ejemplo (caso de que el nombre quepa en pantalla). Una sugerencia sera que
aseis esa funcin para que en caso de ser un carcter no soportado, visualizara un carcter comodn (? por
plo), y que trabajaseis con Utf-8 siempre y solo convirtierais para visualizar (pero all cada uno).
LIBFAT: Inicializando
se inicializa con sta funcin:
fatInit(8, false);
trata de montar una serie de dispositivos en unidades con nombres como "fat0:","fat1:",..., "fat4:"... pero permite
fatSetDefaultInterface(PI_INTERNAL_SD);
.h podis ver una serie de dispositivos definidos como PI_algo, como PI_SDGECKO_A, o PI_USBSTORAGE.
mbargo, si vais a acceder a varios dispositivos fat, es preferible utilizar el nombre original de la "particin" a estar
char name[]="fatX:/test.txt";
con lo que tenemos hasta ahora, podramos funcionar sin problemas, pero por cuestiones de velocidad de lectura,
e resultar conveniente asignar un tamao interno de cach para la lectura de ficheros. Al mismo tiempo, resulta
niente detectar si los dispositivos estn operativos, pues ciertas operaciones podran colgar directamente la
la.
no liaros mucho:
unos buenos valores. El tamao de pgina y el nmero de paginas, requieren un uso de memoria que si no se
ests pensando por que no se nos explica mas a fondo el uso de stas funciones?. Pues por que el autor original
librera, ha decidido desechar los cambios que tanto rodries cmo Hermes introdujeron y est inmerso en una tarea
spedazar la librera y migrar fuentes, no se sabe muy bien si con la idea de jugar al despiste o que. Solo se puede
que repite viejos errores y arrastra ciertos bugs que darn problemas, sobre todo en multithread. Y que Hermes no
a mover un dedo ni para reportarlos, ni para migrar sus cambios, ni nada de nada, puesto que para Hermes la libfat
samos funciona bien y no le da la gana volver atrs, sobre todo en el tema de la cach. Pero obviamente, sto es
cin y si tu "actualizas" la librera, stas funciones de cach no las encontrars. de asignar un tamao de cache, conviene saber si el dispositivo est en funcionamiento:
{ DIR_ITER *dir; char path[]="fatX:/"; int have_device=0; path[3]=48+PI_INTERNAL_SD; dir = diropen(path); if (dir) { dirclose(dir); have_device|=1; fatEnableReadAhead(PI_INTERNAL_SD, 12, 32); } }
esta forma, podemos saber si el dispostivo SD est operativo (intentando abrir el directorio raiz) y en caso
spositivos USB suelen causar problemas: a veces tardan mucho en inicializarse, no se resetean bien o se tropiezan,
hay veces que les da la neura y eso explica cosas raras que Hermes suele hacer en sus inicializaciones.
emplo, en Wiireader esto es lo que Hermes usa yo para inicializar libfat tratando de pillar dispositivo SD y
sitivo USB:
fatInit(8, false); sleep(2); // espero dos segundos, no se muy bien si para darle tiempo a que el dispositivo negocie o est preparado para funcionar fatSetDefaultInterface(PI_INTERNAL_SD); // usa SD como fat: have_device=0; // bits que me indican la presencia de dispositivos { path_file[3]=48+PI_INTERNAL_SD; // path_file ="fatX:/"; for(n=0;n<5;n++) {// numero de reintentos por si la unidad no est preparada o est "enojada" dir = diropen(path_file);
if (dir) { // monta la cache dirclose(dir); have_device|=1; fatEnableReadAhead(PI_INTERNAL_SD, 12, 32); break; } usleep(200*1000); } path_file[3]=48+PI_USBSTORAGE; // path_file ="fatX:/"; for(n=0;n<5;n++) { // numero de reintentos por si la unidad no est preparada o est "enojada" dir = diropen(path_file); if (dir) { // monta la cache dirclose(dir); have_device|=2; fatEnableReadAhead(PI_USBSTORAGE, 12, 32); break; } usleep(200*1000); } }
alguno lo ve superfluo? Pues tal vez, pero cuando uno est harto de que entras desde un RESET y no te ve el
drive", sales y entras y ahora s y cosas as, acabas por ver demonios por todas partes y si hacindolo as.
#include "sys/dir.h" ..... DIR_ITER * dir; static char namefile[256*4]; // reserva espacio suficiente para UTF-8 static struct stat filestat; dir = diropen("fat:/"); // abre el directorio raiz de la unidad fat: while(1) {
if(dirnext(dir, namefile, &filestat)!=0) break; // si no hay mas entradas en el directorio, sal if(filestat.st_mode & S_IFDIR) { // es el nombre de un directorio // namefile contiene el nombre del directorio en formato UTF8,que puede ser "." o ".." tambien } else { // namefile contiene el nombre del fichero en formato UTF-8 } } dirclose(dir); // cierra el directorio
OPERACIONES CON FICHEROS
us de iniciar libfat y comprobar que los dispositivos estn operativos, podis utilizar las libreras estndar ANSI-C
emplo con:
creara el directorio "jamacuco" en raiz de la SD, fijando los correspondientes permisos. Quiz no sea buena idea
at3 as directamente, pues si algn lumbreras cambia el orden de los dispositivos maana , fat3 quizs apunte a
nes alguna duda sobre las libreras estndar, pasaos por aqu y lo miris ms a fondo.
EL DESMONTAJE DE DISPOSITIVOS
ta conveniente asegurarse antes de salir de un programa de que todos los ficheros abiertos, se han cerrado y de
e puede hacer aadiendo esto a la funcin de salida que definimos con at_exit():
if(have_device & 1) fatUnmount(PI_INTERNAL_SD); // desmonta la SD if(have_device & 2) fatUnmount(PI_USBSTORAGE); // desmonta dispostivo USB
funciones las modific Hermes para que flushearn los datos incluso en el caso de que se detectaran ficheros
os antes de salir y no se pudieran desmontar los dispositivos. Si cerris los ficheros, se flushearn los buffers
icados, pero existen ciertas operaciones como crear directorios, borrarlos, etc, que pueden dejar alguna operacin
ente de escritura desde la cach al dispositivo y por eso es aconsejable desmontarlo. esto , tienes todo lo necesario para trabajar con ficheros.
nclude <unistd.h> podemos encontrar dos interesantes funciones para "perder" el tiempo:
unsigned sleep(unsigned int __seconds ); // espera X segundos durmiendo el hilo int usleep(useconds_t __useconds); // espera X microsegundos durmiendo el hilo
plos:
plicacin correcta de sta funcin sera: "Suspende la ejecucin del hilo (del programa) permitiendo que otro hilo
a tomar el control y al cabo de X tiempo, trata de continuar con la ejecucin del hilo en cuanto sea posible".
cir, que al margen de la precisin que pueda tener el temporizador, el tiempo que tarda el hilo en volver a
rtar, depender de si hay un hilo con mayor prioridad que est funcionando en el momento de cumplirse el plazo o
e una funcin interna en la librera libogc, que no est incluida en ningn fichero de cabecera (de ah que se
uncin pierde el tiempo de forma similar a usleep, pero no duerme el hilo. La importancia de este matiz la podris
render cuando se hable de la programacin multithread pero os basta con saber que si un hilo no suspende (o
me) su ejecucin, no puede ser interrumpido por otro hilo salvo que ese nuevo hilo tenga una prioridad mayor.
o queda patente que usleep() permite la ejecucin de hilos que estn a la espera de inferior o igual prioridad,
na funcin llamada gettick() a nivel interno que nos devuelve un contador de 32 bits de tiempo. Esta funcin no
presente en ningn fichero de cabecera (si os interesa curiosear el fuente de las funciones, est en
unsigned gettick();
tick() devuelve "ticks" que hay que convertir a medidas de tiempo humanas.
odemos encontrar en /ogc/lwp_watchdog.h una serie de definiciones para realizar esa conversin:
((ticks)/TB_TIMER_CLOCK)
odemos medir el intervalo de tiempo entre dos medidas, haciendo la diferencia y usarlo para hacer los ajustes
arios en el programa (por ejemplo, actualizar movimientos de nuestros sprites de forma independiente al refresco
mbargo, existe una funcin que hace ms o menos lo mismo que gettick(), pero trabajando con medidas de 64 bits
enciona aqu por si necesitais precisin extra. Parece que gettime() gasta un tiempo en hacer la lectura).
a funcin devuelve 0 si todo fue bien y los datos en una estructura timespec que se define as:
struct timespec { time_t tv_sec; /* Seconds */ // unsigned long (u32) long tv_nsec; /* Nanoseconds */ // (s32) };
a le falta un parmetro con respecto a la definicin de la funcin en time.h y se supone que equivale a
, se deja dicho para que vosotros lo sepis. Eso si, ste tipo de funciones no estn pensadas para ser llamadas en
frame, porque suelen ser lentas al acceder al reloj de tiempo real, as que haced un uso responsable de ella.
TIMERS PROGRAMABLES
ema te permite definir una serie de alarmas de tiempo que vienen muy bien para trabajar con pasos regulares. un ojo a ogc/system.h ahora hemos visto funciones que nos permiten perder el tiempo y otras que nos permiten medir un intervalo, pero
nguna se puede estar seguro de que el tiempo transcurrido es el esperado, (dependiendo de la precisin del
orizador, claro).
lo las alarmas son especialmente tiles ya que te permiten programar un tiempo que una vez transcurrido, produce
nterrupcin que nos lleva a una callback de tratamiento donde teniendo cuidado, eso s (recordemos que estamos
e esa callback por ejemplo, podemos "despertar" hilos de una forma regular (Hermes por ejemplo emplea una
a en su juego Guitarfun para actualizar graficos/leer pad cada 20 ms, mientras el hilo de fondo, descodifica y
duce Ogg y de esa forma, el programa trabaja de forma independiente al refresco de frames (50/60 Hz) para cada
to de imagen).
crear una alarma, primero necesitamos dos estructuras, una para alojar la alarma:
syswd_t myalarm;
ta forma:
SYS_CreateAlarm(&myalarm);
uncin devuelve 0 si la alarma se cre correctamente, pero ahora toca ponerla en funcionamiento con alguna de
dos funciones:
s32 SYS_SetAlarm(syswd_t thealarm,const struct timespec *tp,alarmcallback cb); // una sola vez s32 SYS_SetPeriodicAlarm(syswd_t thealarm,const struct timespec *tp_start,const struct timespec *tp_period,alarmcallback cb); // peridicamente
primera ajusta la alarma para un nico uso pero nada impide para que dentro de la callback programes de nuevo la
a usando SetAlarm con el mismo u otro tiempo y repetir (o en cualquier otro punto). La segunda usa un
orizador inicial y luego otro que se utilizar para llamar de forma peridica a la callback thealarm: estructura alarm inicializada con SYS_CreateAlarm(). tp, tp_start, tp_periodic: estructuras timespec con el tiempo a programar. cb: callback que ser llamada al transcurrir el tiempo.
os algunos ejemplos:
syswd_t myalarm; struct timespec alarm_time; void alarm_handler() { // aqui llega cuando se cumpla el tiempo: recuerda que esto es una callback de interrupcion }
...... alarm_time.tv_sec=0; alarm_time.tv_nsec=20*1000*1000; // 20 milisegundos (notese que tv_nsec es en nanosegundos) SYS_CreateAlarm(&myalarm); SYS_SetAlarm(myalarm,&alarm_time,alarm_handler);
syswd_t myalarm; struct timespec alarm_time; int flip=0; void alarm_handler() { // aqui llega cuando se cumpla el tiempo: recuerda que esto es una callback de interrupcion alarm_time.tv_sec=0; if(flip) alarm_time.tv_nsec=20*1000*1000; // 20 milisegundos (notese que tv_nsec es en nanosegundos) else alarm_time.tv_nsec=40*1000*1000; // 40 milisegundos (notese que tv_nsec es en nanosegundos) flip^=1; SYS_SetAlarm(myalarm,&alarm_time,alarm_handler); // reprogramamos la alarma desde la callback } ...... alarm_time.tv_sec=0; alarm_time.tv_nsec=20*1000*1000; // 20 milisegundos (notese que tv_nsec es en nanosegundos) SYS_CreateAlarm(&myalarm); SYS_SetAlarm(myalarm,&alarm_time,alarm_handler);
syswd_t myalarm; struct timespec alarm_time; struct timespec alarm_time2; int flip=0; void alarm_handler() { // aqui llega cuando se cumpla el tiempo: recuerda que esto es una callback de interrupcion // aqui llega despues de dos segundos y luego, cada 20 milisegundos } ...... alarm_time.tv_sec=2; // 2 segundos alarm_time.tv_nsec=0; alarm_time2.tv_sec=0; alarm_time2.tv_nsec=20*1000*1000; // 20 milisegundos (notese que tv_nsec es en nanosegundos) SYS_CreateAlarm(&myalarm); SYS_SetPeriodicAlarm(myalarm,&alarm_time, &alarm_time2, alarm_handler); // aguarda 2 segundos y a apartir de ah, llama la callback cada 20 ms
LIBERANDO/CANCELANDO ALARMAS
armas que podemos programar, son finitas. No se sabe cual ser el nmero exacto pero obviamente, lo podis
ncin:
puedes usar para cancelar alarmas ya programadas y en curso, mientras que la funcin:
Programacin Multihilo
i la programacin multihilo consiste en la posibilidad de interrumpir la ejecucin de un hilo para tratar otro o en la
lidad de aprovechar los tiempos muertos de un hilo, para gestionar otras cosas desde otro hilo.
milagros no existen y la CPU slo puede ocuparse de uno de ellos en cada momento, por lo que no se trata de
tar varias tareas de forma simultnea, si no de aprovechar los tiempos muertos como digo, o tratar una serie de
os conectados a las interrupciones desde un hilo, de forma que ese hilo interrumpe a otro para realizar alguna tarea
los tienen asignados todos un nivel de prioridad. Esta prioridad se utiliza para determinar que hilo toma el control y
la consiste en que el hilo que tiene mayor prioridad y est en espera de ser activado, se hace con el control.
osa que tenis que tener en cuenta y manejar con sumo cuidado, es el tema de las reentradas en las funciones.
o el tema de utilizar funciones del mismo grupo dentro de una librera o que comparten recursos.
emplo, si desde un hilo se leen los pads con WPAD_ScanPads() y desde otro se comprueban los datos ledos del
ote, es posible que el hilo que lee el Wiimote tome el control en medio de una llamada a WPAD_ScanPads() y reciba errneos y en otros casos, se pueden obtener una serie de paradojas.
estra funcin utiliza nicamente registros del procesador y variables locales o globales que no se modifican, no
is problemas en utilizarla en multihilo, puesto que cada hilo tiene su propia pila y los registros se salvan
s de cambiar de hilo. Pero si vuestra funcin accede a variables globales que se modifican, acceden a registros del
ware o a dispositivos, en definitiva todo lo que sea compartir recursos, entonces no estar preparada para un acceso
LA CREACIN DE UN HILO
void * thread_main(void *arg) { // arg recibe el puntero arg que asignamos al crear el hilo return NULL; // aqui se sale del hilo }
dentemente, sera un hilo muy fugaz y su utilidad sera bastante baja. La cosa mejorara aadindole un bucle de
int exit_thread_main=0; void * thread_main(void *arg) { // arg recibe el puntero arg que asignamos al crear el hilo while(1) { usleep(20000); // aguarda 20 ms if(exit_thread_main) break; /* aqui haces lo que te de la gana */ } return NULL; // aqui se sale del hilo }
observamos que este hilo ha sido pensado para tener un prioridad mayor que la de main(), para que mas o menos,
20 milisegundos se active (usleep() como ya se explic, duerme el hilo y luego trata de despertarlo) y haga lo que que hacer en un bucle infinito (luego la tarea sea hara cada 20 ms+ lo que tarde el resto, que puede ser una
da de tiempo), pero que tiene una variable, exit_thread_main la cual asignndola a un valor no cero, rompe el
variable de control es muy importante puesto que este hilo por si solo no va a poder salir y eso dara lugar a un
ue de la mquina cuando pretendas volver al HBC por ejemplo. As pues, antes de volver deberas asignar
hread_main=1; y asegurarte que el hilo est 'despierto' para que se produzca ese break.
funcin thread_main de antes, hemos observado un mtodo que supone que el hilo va a su bola desactivndose por
io de 20 ms y luego volvindose activar. Sin embargo, seguramente os resultar mas til activar el hilo de forma
int exit_thread_main=0; void * thread_main(void *arg) { // arg recibe el puntero arg que asignamos al crear el hilo while(1) { if(exit_thread_main) break; // si ste es nuevo LWP_ThreadSleep(thread_queue); cola para poder despertarlo if(exit_thread_main) break; /* aqui haces lo que te de la gana */ } return NULL; // aqui se sale del hilo }
:D
se puede apreciar, ahora el hilo estar controlado por esa cola y necesitaremos decirle desde fuera que puede
nuar.
LWP_ThreadSignal(thread_queue);
cerlo, en el momento en que las reglas de prioridad lo permitan, nuestro hilo despertar, har su tarea y al
oneros algunos ejemplos, en el programa Guitarfun se usa el mtodo de las colas para actualizar los grficos cada
s mediante una alarma, los reproductores de audio esperan la activacin del hilo de sta forma para rellenar con
cnica de usleep() que usbamos al principio, se usa en Guitarfun tambin, para el nuevo hilo que se encarga de
ar los buffers de lectura de los ficheros Ogg, en lecturas de 256KB, tarea que puede consumir varios segundos y
mos visto que si retornamos desde la funcin de inicio del hilo, salimos de l, pero si queremos salir de un hilo el principal o vamos a salir al sistema, conviene seguir los siguientes pasos:
exit_thread_main=1; // asignamos a 1 la variable que rompe el bucle while() LWP_ThreadSignal(thread_queue); // si estamos usando colas, forzamos que el hilo despierte en caso de estar dormido en la cola LWP_JoinThread(h_my_thread, NULL); // pasamos el handle del hilo (lwp_t) del que esperaremos su finalizacin LWP_CloseQueue(thread_queue); // cerramos la cola
lgo fallara aqu, la aplicacin se colgara, as que conviene asegurarse de que ninguna otra cosa bloquear la salida
o.
d mucho cuidado eso si, con usar funciones que se interfieran con el uso de varios hilos
CAMBIANDO LA PRIORIDAD
habis visto, en Wii los hilos obedecen a temas prioridad a la hora de activarse, siendo el hilo de mayor prioridad
s abusn de la clase, no permitiendo la ejecucin de otros hilos salvo que el mismo se libere.
as veces, resulta interesante cambiar la prioridad de un hilo para forzar que tome el control y luego bajrsela, para
tir que otro le pueda interrumpir o simplemente, fijar una prioridad muy alta para que otro hilo no pueda
umpir al hilo actual hasta que acabe de hacer algo, momento en el cual restableceremos la prioridad.
esgracia en Wii hay un problema raro que afecta a la programacin multihilo y parece ser que a los nmeros
tes. Dicho problema se corrigi parcialmente, pero no del todo, as que puede ser conveniente proteger ciertas
dad 40
mos dicho que cambiando la prioridad, podramos proteger una determinada seccin de un hilo contra el cambio de
solicitado desde una callback (interrupciones), pero hay puntos donde por conveniencia es mejor deshabilitar las
as interrupciones.
u32 IRQ_Disable(); // deshabilita las interrupciones y devuelve el estado void IRQ_Restore(u32 level); // restaura las interrupciones con el estado devuelto por IRQ_Disable()
u32 stat; stat=IRQ_Disable(); // deshabilita /* hacer algo que no lleve mucho tiempo, ni requiera interrupciones habilitadas */ IRQ_Restore(stat); // restaura
EL USO DE SEMFOROS
emforos se utilizan para regular el paso de los hilos hacia una funcin o funciones que solo permiten el acceso a un
ada vez, debido a una serie de caractersticas propias de esa funcin o funciones. Por ejemplo, si se va leer desde
podis programar el dispositivo para que os lea un sector y el dispositivo tardar un tiempo en "servroslo", pero no
s leer sectores no consecutivos de forma simultanea, ni el dispositivo puede atender mas peticiones hasta que haya
ado con la primera (ya que solo tiene "dos manos" hablando virtualmente).
ues, se necesita un mecanismo que haga que si dos o mas hilos acceden a una funcin protegida, solo uno pase y el
queden a la espera hasta que finalice el hilo que pas primero. Y de eso se encargan los semforos (un ejemplo lo
crear uno, primero tenemos que crear un identificador tal que as (ver ogc/mutex.h):
mutex_t mi_semaforo=LWP_MUTEX_NULL;
cializamos as:
LWP_MutexInit(&mi_semaforo, false);
LWP_MutexDestroy(mi_semaforo);
ya tenemos nuestro semforo creado, y ahora, cmo se usa en la prctica?. Pues para eso tenemos stas dos
ones:
s32 LWP_MutexLock(mutex_t mutex); // bloquea mediante el semforo s32 LWP_MutexUnlock(mutex_t mutex); // desbloquea el semforo
amos un ejemplo:
int
LWP_MutexLock(mi_semaforo); // bloquea el paso de hilos. De forma inicial al crear el semforo, permite el paso de un hilo /* codigo protegido por el semaforo */ ...... ...... LWP_MutexUnlock(mi_semaforo); // desbloquea el semaforo y permite el paso del hilo siguiente a la funcin return ret; }
se puede apreciar, es un mecanismo muy sencillo. Aplicndolo a todas las funciones de una librera que sean
ibles, podemos evitar el acceso de otros hilos a una librera mientras est en uso por parte de uno de ellos, pero
no se traduce en un error de forma que la librera diga algo as "lo siento, pero estoy en uso: vuelva usted mas
", si no que el semforo lo convierte en "en est momento la funcin est ocupada, aguarde en la sala de espera que el hilo pepito abandone la funcin".
claro, es responsabilidad del hilo "pepito" informar con MutexUnlock de que otros hilos pueden acceder a ella. y
n es importante que el hilo "pepito" evite volver a pasar por MutexLock, puesto que ya no dispone de tarjeta de y ser bloqueado tambin.
sto es todo lo que tenis que saber sobre los semforos, los hilos y el copn de vino para trabajar con hilos. El resto
a vuestra. Categoras:
Wii
Hemos
movido el contenido de este artculo a la wiki. Si deseas contribuir con el artculo o
i b a
ThemAsiiveStyleAdicto
Que maldita Currada de tuto tio, esta tarde me lo leoi haver si puedo hacer algo!!
A r r i b a
Hermes
MegaAdicto!!!
necesario emplear un estndar para poder manejar texto e intercambiar la informacin y el estndar elegido fue el ASCII que se defini entre 1963-1966 y posteriormente, se redefini en 1986 dando lugar a la especificacin ANSI que conocemos actualmente. ASCII se compona de 128 caracteres, usando 7 bits por tanto, donde se le aada un ltimo bit como bit de paridad en las transmisiones, dado que ste estndar se pens inicialmente para telegrafa. De esos 128 caracteres, los primeros 33 se utilizaban para cdigos de control no imprimibles. As por ejemplo, chr 8 representa un espacio atrs, chr 9 el tabulador, chr 10 avance de lnea , chr 13 el retorno de carro y chr 32 el espacio. De los imprimibles, el caracter '0' es chr 48, la 'A' es chr 65, y la 'a' chr 97 En algunos sistemas (LINUX/UNIX) el carcter 13 se toma como avance de lnea y retorno de carro de forma simultanea y esa es la razn por las que algunas veces, si abrimos un README desde el Wordpad de Windows, el texto se apelotona espera que las lneas terminen con los caracteres 10 y 13) Sin embargo, pronto se hizo necesario ampliar el set de caracteres, dado que ASCII recoga los caracteres latinos USA pero por ejemplo, no recoga la , ni los caracteres de acentuacin que nosotros conocemos. Esto se recoge cmo una extensin que ocupa los caracteres desde el 128 al 255 (usando lo que antes era un bit de paridad) conocido como estndar ISO-8859-1 y as en solo 8 bits, se recogan todos los caracteres especiales de origen latino. screenlib utiliza una captura de caracteres que contiene 224 caracteres (elimina los 32 primeros caracteres ASCII, pero conserva el carcter espacio) y que recogen esa especificacin ANSI con los caracteres extendidos ISO-8859-1 Puesto que estos estndares solo recogen caracteres latinos, se hizo necesario crear otros estndares que recogieran otros tipos de caracteres para soportar otros idiomas, como por ejemplo, el ruso y as naci el Unicode. Dentro del Unicode existe una especificacin llamada Utf-8 (8-bit Unicode Transformation Format) que utiliza un sistema de longitud variable para que partiendo de 8 bits, se puedan codificar todos los caracteres Unicode. Esta especificacin es la que utiliza la librera libfat a la hora de trabajar con nombres de ficheros, pero como nos afecta? (porque
Pues bien, Utf-8 utiliza el bit de mayor peso, el que actualmente recoge los caracteres ISO-8859-1 para cambiar y codificar la tabla de caracteres a utilizar. Y al ser de longitud variable, un carcter puede ocupar de 1 a 4 bytes En Unicode, los primeros 256 caracteres son los mismos que los especificados en la ISO-8859, por lo que los caracteres Utf-8 del 0 al 127, corresponderan directamente a lo que nosotros conocemos. Lo que cambia es la forma de tratar el bit de mayor peso y eso compromete los nombres de los ficheros que utilicen acentos, etc. As que tenis dos opciones: 1) O bien procuris utilizar nombres de ficheros/directorios que no usen caracteres fuera del rango de 128 caracteres definido por el estandar ANSI 2) O si no os queda ms remedio, tendris que convertir de Utf-8 a ISO-8859 para imprimir o de ISO-8859 a Utf-8 para manejar nombres de ficheros/directorios que usen esos caracteres especiales. En mi caso en mi aplicacin Wiireader, a mi slo me interesa ver el nombre de los ficheros correctamente al visualizarlos en pantalla, dado que al listar ficheros desde libfat, los obtengo en Utf-8 y por tanto, al abrirlos conservar ese Utf-8 As pues, como sabemos que los caracteres del 0 a 127 en Utf-8 son iguales y ocupan solo un byte, solo nos restara saber como se codifican los caracteres del 128 al 255 para poder imprimirlos correctamente desde la screenlib En http://es.wikipedia.org/wiki/UTF-8 podemos ver que para cubrir el rango de caracteres 0x80 a 0x7ff (nosotros solo necesitamos de 0x80 a 0xff), se utilizan dos bytes:
rango: 000080 - 0007FF Unicode (UTF-16) 00000xxx xxxxxxxx Utf-8: 110xxxxx 10xxxxxx
As pues, podramos hacer una rutina de conversin de Utf-8 a ISO-8859 de esta forma:
void UTF8_To_ISO8859(u8 *src, *u8 dst) { while(1) { if(src[0]==0) {*dst=0;break;} // fin de cadena
if((src[0] & 128)==0) {*dst++=*src++;} // rango de 0x0 a 0x7f else if((scr[0] & 224) ==192 && (scr[1] & 192)==128) // rango 0x80 a 0x7ff { if(src[0] & 0x1c) {*dst=0;break;} // error fuera del rango 0x80 a 0xff: caracteres no soportados
*dst++= (((src[0] & 3)<<6) | (src[1] & 63)); src+=2; } else // error fuera del rango de 0x0 a 0x7ff o no usa especificacin UTF-8 { *dst=0;break; } } }
Con esta rutina (que por cierto, acabo de crear aqu y no ha sido testeada :twisted:) podris convertir cadena utf-8 como fuente, a otra ISO como destino, para visualizarla con s_printf por ejemplo (caso de que el nombre quepa en pantalla ). Mi sugerencia sera que ampliases esa funcin para que en caso de ser un carcter no soportado, visualizara un carcter comodn (? por ejemplo), y que trabajases con Utf-8 siempre y solo convirtieras para visualizar (pero all cada uno )
********************************************************************* ******************************
LIBFAT: Inicializando
fatInit(8, false);
El 8 establece una cach de 8 clusters para entradas de directorios y cosas as. Libfat trata de montar una serie de dispositivos en unidades con nombres como "fat0:","fat1:",..., "fat4:"... pero permite asignar uno de esos dispoitivos cmo unidad por defecto "fat:". Lo normal es que esa unidad la asignis a la SD, de sta forma:
fatSetDefaultInterface(PI_INTERNAL_SD);
En fat.h podis ver una serie de dispositivos definidos como PI_algo, como PI_SDGECKO_A, o PI_USBSTORAGE. Sin embargo, si vais a acceder a varios dispositivos fat, es preferible utilizar el nombre original de la "particin" a estar intercambiado el interface por defecto. Asi pues, imaginemos que tenemos esta cadena: char name[]="fatX:/test.txt"; podramos especificar la unidad directamente, modificando la cadena de sta forma: name[3]=48+PI_INTERNAL_SD; (48 es el carcter ASCII '0') Detectando las particiones y asignando el tamao de la cach de lectura
Bien, con lo que tenemos hasta ahora, podramos funcionar sin problemas, pero por cuestiones de velocidad de lectura, puede resultar conveniente asignar un tamao interno de cach para la lectura de ficheros. Al mismo tiempo, resulta conveniente detectar si los dispositivos estn operativos, pues ciertas operaciones podran colgar directamente la consola. Para asignar el tamao de la cache de lectura, se utiliza sta funcin:
Son unos buenos valores. El tamao de pgina y el nmero de paginas, requieren un uso de memoria que si no se administra bien, se desperdicia intilmente, por lo que no es conveniente abusar. Quiz ests pensando por que no nos explica mas a fondo el uso de stas funciones? Pues por que el autor original de la librera, ha decidido desechar los cambios que tanto rodries cmo yo introdujimos y est inmerso en una tarea de despedazar la librera y migrar fuentes, no se muy bien si con la idea de jugar al despiste o que: solo puedo decir que repite viejos errores y arrastra ciertos bugs que darn problemas, sobre todo en multithread. Y que yo no pienso mover un dedo ni para reportarlos, ni para migrar mis cambios, ni nada de nada, puesto que para m la libfat que usamos funciona bien y no me da la gana volver atrs, sobre todo en el tema de la cach. Pero obviamente, sto es mi opcin y si tu "actualizas" la librera, stas funciones de cach no las encontrars.
De esta forma, podemos saber si el dispostivo SD est operativo (intentando abrir el directorio raiz) y en caso afirmativo, ajustamos un flag de presencia y activamos la cach de lectura. Los dispositivos USB suelen causar problemas: a veces tardan mucho en inicializarse, no se resetean bien o se tropiezan o yo que se, pero hay veces que les da la neura y eso explica cosas raras que suelo hacer en mis inicializaciones. Por ejemplo, en Wiireader esto es lo que uso yo para inicializar libfat tratando de pillar dispositivo SD y dispositivo USB:
fatInit(8, false);
sleep(2); // espero dos segundos, no se muy bien si para darle tiempo a que el dispositivo negocie o est preparado para funcionar
for(n=0;n<5;n++) // numero de reintentos por si la unidad no est preparada o est "enojada" { dir = diropen(path_file); if (dir) {dirclose(dir); have_device|=1;fatEnableReadAhead(PI_INTERNAL_SD, 12, 32);break;} // monta la cache usleep(200*1000); }
for(n=0;n<5;n++) // numero de reintentos por si la unidad no est preparada o est "enojada" { dir = diropen(path_file); if (dir) {dirclose(dir); have_device|=2;fatEnableReadAhead(PI_USBSTORAGE, 12, 32);break;} // monta la cache usleep(200*1000); } }
Que alguno lo ve superfluo? Pues tal vez, pero cuando uno est hasta los cojones de que entras desde un RESET y no te ve el "penedrive", sales y entras y ahora s y cosas as, acabas por ver demonios por todas partes y si hacindolo as, no te pasa que haras tu? ********************************************************************* ******************************
DIR_ITER * dir;
while(1) { if(dirnext(dir, namefile, &filestat)!=0) break; // si no hay mas entradas en el directorio, sal
if(filestat.st_mode & S_IFDIR) // es el nombre de un directorio { // namefile contiene el nombre del directorio en formato UTF-8,que puede ser "." o ".." tambien } else { // namefile contiene el nombre del fichero en formato UTF-8 } }
Operaciones con ficheros Despus de iniciar libfat y comprobar que los dispositivos estn operativos, podis utilizar las libreras estndar ANSI-C referente a dispositivos para crear/borrar/renombrar/fijar directorios. Por ejemplo con:
Se creara el directorio "jamacuco" en raiz de la SD, fijando los correspondientes permisos. Quiz no sea buena idea usar fat3 as directamente, pues si algn lumbreras cambia el orden de los dispositivos maana , fat3 quiz apunte a otra cosa (de ahi eso de usar puntero[3]=48+PI_torreo) Para leer/escribir ficheros, podis utilizar fopen/fread/fwrite/fseek/ftell/fclose... Vamos, que si tienes alguna duda sobre las libreras estndar, psate por aqu y te lo miras ms a fondo :
http://c.conclase.net/librerias/index.php El Desmontaje de dispositivos Resulta conveniente asegurarse antes de salir de un programa de que todos los ficheros abiertos, se han cerrado y de que las cachs de escritura se han "flusheado". Resulta conveniente desmontar los dispositivos: Eso se puede hacer aadiendo esto a la funcin de salida que definimos con at_exit():
if(have_device & 1) fatUnmount(PI_INTERNAL_SD); // desmonta la SD if(have_device & 2) fatUnmount(PI_USBSTORAGE); // desmonta dispostivo USB
Estas funciones las modifiqu para que flushearn los datos incluso en el caso de que se detectaran ficheros abiertos antes de salir y no se pudieran desmontar los dispositivos. Si cierras los ficheros, se flushearn los buffers modificados, pero existen ciertas operaciones como crear directorios, borrarlos, etc, que pueden dejar alguna operacin pendiente de escritura desde la cach al dispositivo y por eso es aconsejable desmontarlo. Y con esto y un bizcocho, tienes todo lo necesario para trabajar con ficheros
********************************************************************* ******************************
unsigned sleep(unsigned int __seconds ); // espera X segundos durmiendo el hilo int usleep(useconds_t __useconds); // espera X microsegundos durmiendo el hilo
Ejemplos:
La explicacin correcta de sta funcin sera: "Suspende la ejecucin del hilo (del programa) permitiendo que otro hilo pueda tomar el control y al cabo de X tiempo, trata de continuar con la ejecucin del hilo en cuanto sea posible". Es decir, que al margen de la precisin que pueda tener el temporizador, el tiempo que tarda el hilo en volver a despertar, depender de si hay un hilo con mayor prioridad que est funcionando en el momento de cumplirse el plazo o no. Existe una funcin interna en la librera libogc, que no est incluida en ningn fichero de cabecera (de ah que la denomine interna) llamada udelay, que podeis declararla as:
Esta funcin pierde el tiempo de forma similar a usleep, pero no duerme el hilo. La importancia de este matiz la podris comprender cuando hable de la programacin multithread pero os basta con saber que si un hilo no suspende (o duerme) su ejecucin, no puede ser interrumpido por otro hilo salvo que ese nuevo hilo tenga una prioridad mayor. Luego queda patente que usleep() permite la ejecucin de hilos que estn a la espera de inferior o igual prioridad, mientras que udelay() no. Medida Relativa del tiempo Hay una funcin llamada gettick() a nivel interno que nos devuelve un contador de 32 bits de tiempo. Esta funcin no est presente en ningn fichero de cabecera (si os interesa curiosear el fuente de las funciones, est en libogc/timesupp.c) por lo que tendris que definirla as:
unsigned gettick();
gettick() devuelve "ticks" que hay que convertir a medidas de tiempo humanas. As podemos encontrar en /ogc/lwp_watchdog.h una serie de definiciones para realizar esa conversin:
((ticks)/TB_TIMER_CLOCK)
As podemos medir el intervalo de tiempo entre dos medidas, haciendo la diferencia y usarlo para hacer los ajustes necesarios en el programa (por ejemplo, actualizar movimientos de nuestros sprites de forma independiente al refresco de la pantalla y cosas asi) Sin embargo, existe una funcin que hace ms o menos lo mismo que gettick(), pero trabajando con medidas de 64 bits en lugar de los 32 bits que devuelve gettick() y que se define en /ogc/lwp_watchdog.h
Te lo menciono aqu por si necesitas precisin extra, pero para mi uso particular, con gettick() me es suficiente (y parece que gettime() gasta un tiempo en hacer la lectura) Reloj de Tiempo Real En libogc/timesupp.c hay una funcin:
Esta funcin devuelve 0 si todo fue bien y los datos en una estructura timespec que se define as:
struct timespec { time_t tv_sec; /* Seconds */ // unsigned long (u32) long tv_nsec; /* Nanoseconds */ // (s32) };
Yo no he empleado sta funcin nunca, pero de entrada, le falta un parmetro con respecto a la definicin de la funcin en time.h y supongo que equivale a clock_gettime(CLOCK_REALTIME, &time); en otros sistemas. En fin, lo dejo dicho para que vosotros lo sepis: eso si, ste tipo de funciones no estn pensadas para ser llamadas en cada frame, porque suelen ser lentas al acceder al reloj de tiempo real, as que haced un uso responsable de ella. Timers programables El sistema te permite definir una serie de alarmas de tiempo que vienen muy bien para trabajar con pasos regulares. Echad un ojo a ogc/system.h Hasta ahora hemos visto funciones que nos permiten perder el tiempo y otras que nos permiten medir un intervalo, pero en ninguna se puede estar seguro de que el tiempo transcurrido es el esperado, (dependiendo de la precisin del temporizador, claro) Por ello las alarmas son especialmente tiles ya que te permiten programar un tiempo que una vez transcurrido, produce una interrupcin que nos lleva a una callback de tratamiento donde teniendo cuidado, eso s (recordemos que estamos en tiempo de interrupcin), podemos ajustar lo que sea necesario. Desde esa callback por ejemplo, podemos "despertar" hilos de una forma regular (yo por ejemplo, empleo una alarma en mi juego Guitarfun para actualizar graficos/leer pad cada 20 ms, mientras el hilo de fondo, descodifica y reproduce Ogg y de esa forma, el programa trabaja de forma independiente al refresco de frames (50/60 Hz) para cada formato de imagen) La creacin de una alarma Para crear una alarma, primero necesitamos dos estructuras, una para alojar la
alarma:
syswd_t myalarm;
De esta forma:
SYS_CreateAlarm(&myalarm);
la funcin devuelve 0 si la alarma se cre correctamente, pero ahora toca ponerla en funcionamiento con alguna de estas dos funciones:
s32 SYS_SetAlarm(syswd_t thealarm,const struct timespec *tp,alarmcallback cb); // una sola vez
s32 SYS_SetPeriodicAlarm(syswd_t thealarm,const struct timespec *tp_start,const struct timespec *tp_period,alarmcallback cb); // peridicamente
La primera ajusta la alarma para un nico uso pero nada impide para que dentro de la callback programes de nuevo la alarma usando SetAlarm con el mismo u otro tiempo y repetir (o en cualquier otro punto). La segunda usa un temporizador inicial y luego otro que se utilizar para llamar de forma peridica a la callback
-thealarm: Estructura alarm inicializada con SYS_CreateAlarm() -tp, tp_start, tp_periodic: Estructuras timespec con el tiempo a programar. -cb: Callback que ser llamada al transcurrir el tiempo Devuelven 0 si se pudo ajustar la alarma. Veamos algunos ejemplos: Ejemplo de Alarma de una sola vez:
CDIGO: SELECCIONAR TODO
void alarm_handler() { // aqui llega cuando se cumpla el tiempo: recuerda que esto es una callback de interrupcion
......
SYS_CreateAlarm(&myalarm); SYS_SetAlarm(myalarm,&alarm_time,alarm_handler);
int flip=0;
void alarm_handler() { // aqui llega cuando se cumpla el tiempo: recuerda que esto es una callback de interrupcion
alarm_time.tv_sec=0; if(flip) alarm_time.tv_nsec=20*1000*1000; // 20 milisegundos (notese que tv_nsec es en nanosegundos) else alarm_time.tv_nsec=40*1000*1000; // 40 milisegundos (notese que tv_nsec es en nanosegundos)
......
SYS_CreateAlarm(&myalarm); SYS_SetAlarm(myalarm,&alarm_time,alarm_handler);
syswd_t myalarm; struct timespec alarm_time; struct timespec alarm_time2; int flip=0;
void alarm_handler() { // aqui llega cuando se cumpla el tiempo: recuerda que esto es una callback de interrupcion
......
SYS_CreateAlarm(&myalarm); SYS_SetPeriodicAlarm(myalarm,&alarm_time, &alarm_time2, alarm_handler); // aguarda 2 segundos y a apartir de ah, llama la callback cada 20 ms
Liberando/Cancelando alarmas Las alarmas que podemos programar, son finitas. No se cual ser el nmero exacto pero obviamente,lo puedes comprobar creando alarmas hasta que se produzca un error La funcin:
La puedes usar para cancelar alarmas ya programadas y en curso, mientras que la funcin:
Te permite liberar las alarmas creadas con SYS_CreateAlarm(); Y con esto ya tienes lo necesario para gestionar el tiempo de forma apropiada
********************************************************************* ******************************
Programacin Multihilo
En Wii la programacin multihilo consiste en la posibilidad de interrumpir la ejecucin de un hilo para tratar otro o en la posibilidad de aprovechar los tiempos muertos de un hilo, para gestionar otras cosas desde otro hilo. Los milagros no existen y la CPU slo puede ocuparse de uno de ellos en cada momento, por lo que no se trata de ejecutar varias tareas de forma simultnea, si no de aprovechar los tiempos muertos como digo, o tratar una serie de eventos conectados a las interrupciones desde un hilo, de forma que ese hilo interrumpe a otro para realizar alguna tarea y al mismo tiempo, permite el paso de las interrupciones. Los hilos tienen asignados todos un nivel de prioridad. Esta prioridad se utiliza para determinar que hilo toma el control y la regla consiste en que el hilo que tiene mayor prioridad y est en espera de ser activado, se hace con el control. Una cosa que tenis que tener en cuenta y manejar con sumo cuidado, es el tema de las reentradas en las funciones. Incluso el tema de utilizar funciones del mismo grupo
dentro de una librera o que comparten recursos. Por ejemplo, si desde un hilo se leen los pads con WPAD_ScanPads() y desde otro se comprueban los datos ledos del Wiimote, es posible que el hilo que lee el Wiimote tome el control en medio de una llamada a WPAD_ScanPads() y reciba datos errneos y en otros casos, se pueden obtener una serie de paradojas. Si tu funcin utiliza nicamente registros del procesador y variables locales o globales que no se modifican, no tendrs problemas en utilizarla en multihilo, puesto que cada hilo tiene su propia pila y los registros se salvan antes de cambiar de hilo . Pero si tu funcin accede a variables globales que se modifican, acceden a registros del hardware o a dispositivos, en definitiva todo lo que sea compartir recursos, entonces no estar preparada para un acceso multihilo y deberas protegerla mediante uso de semforos (que veremos luego). La creacin de un hilo La funcin LWP_CreateThread() es la encargada de crear los hilos (ver ogc/lwp.h):
-thethread: Puntero de tipo lwp_t que recibe el handle. Por ejemplo: static lwp_t h_my_thread=LWP_THREAD_NULL; -entry: Funcin de entrada del hilo -arg: Puntero con el argumento que recibir la funcin de entrada del hilo (para asignar datos privados). Puede ser NULL -stackbase: Puntero con la direccin base de la pila (alineada a 32). Si NULL, se asigna de forma interna. -stack_size: Tamao de la pila en bytes. Si 0 se asigna 8KB por defecto. -prio: Prioridad del hilo, de 0 a 127 (maxima prioridad). Como referencia el player ogg
utiliza 80 y yo suelo asignar a 40 el main() (no conozco la prioridad de main() pero tampoco es que importe mucho ).
La funcin devuelve 0 si no hubo problema alguno. La funcin de entrada del hilo La funcin de entrada sera algo as:
CDIGO: SELECCIONAR TODO
void * thread_main(void *arg) { // arg recibe el puntero arg que asignamos al crear el hilo
Evidentemente, sera un hilo muy fugaz y su utilidad sera bastante baja. La cosa mejorara aadindole un bucle de control y algo que permitiera oxigenar el resto de hilos:
CDIGO: SELECCIONAR TODO
int exit_thread_main=0;
void * thread_main(void *arg) { // arg recibe el puntero arg que asignamos al crear el hilo
Aqu observamos que este hilo ha sido pensado para tener un prioridad mayor que la de main(), para que mas o menos, cada 20 milisegundos se active (usleep() como ya expliqu, duerme el hilo y luego trata de despertarlo) y haga lo que tenga que hacer en un bucle infinito (luego la tarea sea hara cada 20 ms+ lo que tarde el resto, que puede ser una burrada de tiempo), pero que tiene una variable, exit_thread_main la cual asignndola a un valor no cero, rompe el bucle y eso hara que finalizara el hilo. sta variable de control es muy importante puesto que este hilo por si solo no va a poder salir y eso dara lugar a un cuelgue de la mquina cuando pretendas volver al HBC por ejemplo. As pues, antes de volver deberas asignar exit_thread_main=1; y asegurarte que el hilo est 'despierto' para que se produzca ese break.
Controlando la ejecucin del hilo En la funcin thread_main de antes, hemos observado un mtodo que supone que el hilo va a su bola desactivndose por espacio de 20 ms y luego volvindose activar. Sin embargo, seguramente te resultar mas til activar el hilo de forma mas precisa, desde una callback de una alarma o cualquier otro sitio. Para eso nos ser muy til el uso de colas:
int exit_thread_main=0;
void * thread_main(void *arg) { // arg recibe el puntero arg que asignamos al crear el hilo
if(exit_thread_main) break;
Como se puede apreciar, ahora el hilo estar controlado por esa cola y necesitaremos decirle desde fuera que puede continuar. Para ello, desde una callback podis usar sta funcin:
LWP_ThreadSignal(thread_queue);
Al hacerlo, en el momento en que las reglas de prioridad lo permitan, nuestro hilo despertar, har su tarea y al completar el bucle, volver a dormirse en espera de que llamemos a LWP_ThreadSignal() de nuevo. Por poneros algunos ejemplos, en mi programa Guitarfun se usa el mtodo de las colas para actualizar los grficos cada 20 ms mediante una alarma, los reproductores de audio esperan la activacin del hilo de sta forma para rellenar con samples los buffers, etc. La tcnica de usleep() que usbamos al principio, la uso en Guitarfun tambin, para el nuevo hilo que se encarga de rellenar los buffers de lectura de los ficheros Ogg, en lecturas de 256KB, tarea que puede consumir varios segundos y que por tanto, es despreciable la prdida de ms del usleep y precisa un funcionamiento independiente. Para finalizar una cola se usa:
Salir de los Hilos Ya hemos visto que si retornamos desde la funcin de inicio del hilo, salimos de l, pero si queremos salir de un hilo desde el principal o vamos a salir al sistema, conviene seguir los siguientes pasos:
LWP_ThreadSignal(thread_queue); // si estamos usando colas, forzamos que el hilo despierte en caso de estar dormido en la cola
LWP_JoinThread(h_my_thread, NULL); // pasamos el handle del hilo (lwp_t) del que esperaremos su finalizacin
Si algo fallara aqu, la aplicacin se colgara, as que conviene asegurarse de que ninguna otra cosa bloquear la salida del hilo. No he preparado ejemplos con uso de mltiples hilos, pero mi juego Guitarfun o mi aplicacin Wiireader, muestran varios ejemplos que podis estudiar. Tened mucho cuidado eso si, con usar funciones que se interfieran con el uso de varios hilos Cambiando la prioridad Cmo habis visto, en Wii los hilos obedecen a temas prioridad a la hora de activarse, siendo el hilo de mayor prioridad el ms abusn de la clase, no permitiendo la ejecucin de otros hilos salvo que el mismo se libere. Algunas veces, resulta interesante cambiar la prioridad de un hilo para forzar que tome el control y luego bajrsela, para permitir que otro le pueda interrumpir o simplemente, fijar una prioridad muy alta para que otro hilo no pueda interrumpir al hilo actual hasta que acabe de hacer algo, momento en el cual restableceremos la prioridad. Por desgracia en Wii hay un problema raro que afecta a la programacin multihilo y parece ser que a los nmeros flotantes. Dicho problema se corrigi parcialmente, pero no del todo, as que puede ser conveniente proteger ciertas secciones de sta manera. La funcin para cambiar la prioridad es:
-thethread: Handle del hilo. Si LWP_THREAD_NULL se trata del hilo actual (no confundir con NULL). Tambin puedes usar LWP_GetSelf(); para conocer el Handle del hilo actual. -prio: Nueva prioridad para el hilo
As por ejemplo, si dentro del main() ponemos LWP_SetThreadPriority(LWP_THREAD_NULL, 40); asignamos a main() la prioridad 40 Nota: no existe una funcin equivalente para conocer la prioridad Proteger secciones crticas Ya hemos dicho que cambiando la prioridad, podramos proteger una determinada seccin de un hilo contra el cambio de hilos solicitado desde una callback (interrupciones), pero hay puntos donde por conveniencia es mejor deshabilitar las propias interrupciones. Para ello podemos contar con dos funciones, presentes en ogc/irq.h:
void IRQ_Restore(u32 level); // restaura las interrupciones con el estado devuelto por IRQ_Disable()
u32 stat;
stat=IRQ_Disable(); // deshabilita
IRQ_Restore(stat); // restaura
El uso de semforos
Los semforos se utilizan para regular el paso de los hilos hacia una funcin o funciones que solo permiten el acceso a un hilo cada vez, debido a una serie de caractersticas propias de esa funcin o funciones. Por ejemplo, si voy a leer desde USB, puedes programar el dispositivo para que te lea un sector y el dispositivo tardar un tiempo en "servrtelo", pero no puedes leer sectores no consecutivos de forma simultanea, ni el dispositivo puede atender mas peticiones hasta que haya finalizado con la primera (ya que solo tiene "dos manos" hablando virtualmente ).
As pues, se necesita un mecanismo que haga que si dos o mas hilos acceden a una funcin protegida, solo uno pase y el resto queden a la espera hasta que finalice el hilo que pas primero. Y de eso se encargan los semforos (un ejemplo lo tenis en LIBFAT, que bloquea el acceso a todas sus funciones mediante semforos) Para crear uno, primero tenemos que crear un identificador tal que as (ver ogc/mutex.h):
mutex_t mi_semaforo=LWP_MUTEX_NULL;
Lo inicializamos as:
LWP_MutexInit(&mi_semaforo, false);
El semforo creado devolver 0 si se inici correctamente y permitir el paso de forma inicial. Cuando queramos eliminarlo, de sta forma (lo explico ahora, que si n, luego me olvido )
LWP_MutexDestroy(mi_semaforo);
Bien, ya tenemos nuestro semforo creado Y ahora cmo se usa en la prctica? Pues para eso tenemos stas dos funciones:
Pongamos un ejemplo:
CDIGO: SELECCIONAR TODO
int {
my_funcion(int manolo)
int ret=0;
LWP_MutexLock(mi_semaforo); // bloquea el paso de hilos. De forma inicial al crear el semforo, permite el paso de un hilo
return ret; }
Como se puede apreciar, es un mecanismo muy sencillo: aplicndolo a todas las funciones de una librera que sean accesibles, podemos evitar el acceso de otros hilos a una librera mientras est en uso por parte de uno de ellos, pero esto no se traduce en un error de forma que la librera diga algo as "lo siento, pero estoy en uso: vuelva usted mas tarde", si no que el semforo lo convierte en "en est momento la funcin est ocupada, aguarde en la sala de espera hasta que el hilo pepito abandone la funcin". Pero claro, es responsabilidad del hilo "pepito" informar con MutexUnlock de que otros hilos pueden acceder a ella. y tambin es importante que el hilo "pepito" evite volver a pasar por MutexLock, puesto que ya no dispone de tarjeta de visita y ser bloqueado tambin. Y sto es todo lo que tenis que saber sobre los semforos, los hilos y el copn de vino para trabajar con hilos. El resto es cosa vuestra. FIN