{"id":110,"date":"2025-05-23T15:22:06","date_gmt":"2025-05-23T13:22:06","guid":{"rendered":"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/?p=110"},"modified":"2025-05-22T17:15:28","modified_gmt":"2025-05-22T15:15:28","slug":"aproveita-os-nucleos-do-teu-procesador-en-godot","status":"publish","type":"post","link":"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/2025\/05\/23\/aproveita-os-nucleos-do-teu-procesador-en-godot\/","title":{"rendered":"Aproveita os n\u00facleos do teu procesador en Godot"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"508\" src=\"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-content\/uploads\/sites\/8\/2025\/05\/image-2-1024x508.png\" alt=\"Imaxe do escenario de Metarcade dende o editor. Non \u00e9 facil representar threads nunha imaxe.\" class=\"wp-image-111\" srcset=\"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-content\/uploads\/sites\/8\/2025\/05\/image-2-1024x508.png 1024w, https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-content\/uploads\/sites\/8\/2025\/05\/image-2-300x149.png 300w, https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-content\/uploads\/sites\/8\/2025\/05\/image-2-768x381.png 768w, https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-content\/uploads\/sites\/8\/2025\/05\/image-2-1536x761.png 1536w, https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-content\/uploads\/sites\/8\/2025\/05\/image-2.png 1824w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>O post anterior, sobre <a href=\"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/2025\/05\/09\/dalle-voz-propia-e-dinamica-aos-teus-personaxes-en-godot\/\">engadir voces aos personaxes en Godot<\/a> quedou inconcluso, ao deixar para m\u00e1is adiante as optimizaci\u00f3ns necesarias para executalo nun m\u00f3bil de gama baixa.<\/p>\n\n\n\n<p>Xa dende a propia documentaci\u00f3n de <a href=\"https:\/\/github.com\/timothyqiu\/gdfxr\">GDXFR<\/a> contraindican o uso do plugin como expresei no artigo anterior. A raz\u00f3n \u00e9 que, dados uns par\u00e1metros iniciais, a xeraci\u00f3n do son completo faise de unha vez, impedindo a execuci\u00f3n de calquera outro pedazo de script ata a fin. Dependendo do tama\u00f1o do audio, por tanto, o tempo de espera vai variar.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Unha alternativa inexplorada por agora<\/h2>\n\n\n\n<p>Haber\u00eda unha soluci\u00f3n tan boa como a que vou expo\u00f1er aqu\u00ed, que ser\u00eda dividir a xeraci\u00f3n do audio en pequenas partes e s\u00f3 xerar o necesario para irse escoitando no momento. Os sintetizadores de audio funcionan deste xeito, xa que o son que se escoitar\u00e1 depende da interpretaci\u00f3n do m\u00fasico e canto m\u00e1is tempo te\u00f1en de son xerado m\u00e1is retardo se notar\u00e1 entre presionar unha tecla e escoitar o resultado.<\/p>\n\n\n\n<p>Xa que xerar o son en pequenas partes requerir\u00eda moitos cambios no c\u00f3digo do plugin e hoxe en d\u00eda incluso os m\u00f3biles de m\u00e1is baixa gama te\u00f1en dous ou m\u00e1is n\u00facleos decidinme a utilizar un deles para a xeraci\u00f3n do audio, liberando o principal para a execuci\u00f3n do xogo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">F\u00edos de procesamento<\/h2>\n\n\n\n<p>Como dic\u00eda, case calquera procesador moderno se comp\u00f3n de dous n\u00facleos ou m\u00e1is. Isto ten que ver con que \u00e9 m\u00e1is sinxelo engadir n\u00facleos a aumentar a velocidade. Isto \u00e9 a\u00ednda m\u00e1is importante en casos como os tel\u00e9fonos m\u00f3biles, onde prima un consumo baixo de bater\u00eda. As\u00ed e todo, o dobre de n\u00facleos non implica un aumento da velocidade a menos que se faga bo uso deles.<\/p>\n\n\n\n<p>Para isto est\u00e1n os f\u00edos de procesamento, m\u00e1is co\u00f1ecidos como <em>threads<\/em>. Cando engadimos un novo <em>thread<\/em> ao noso executable, estamos permitindo ao sistema operativo executar c\u00f3digo en paralelo reutilizando a mesma memoria e recursos do proceso existente. Isto ten de bo que \u00e9 moi r\u00e1pido de comezar un novo f\u00edo de execuci\u00f3n, comparado con comezar un novo executable, e que non se precisa de copiar a memoria entre un f\u00edo e outro, xa que se comparte toda.<\/p>\n\n\n\n<p>Por outra banda, ao compartirse toda a memoria \u00e9 moi f\u00e1cil causar erros, ao modificar nun f\u00edo memoria que se est\u00e1 a ler no outro por exemplo. Xestionar isto con coidado \u00e9 bastante complexo e, a\u00ednda que neste artigo abordarei un modo bastante simple de levalo a cabo, diferentes necesidades van a ter soluci\u00f3ns moi distintas.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Programaci\u00f3n as\u00edncrona<\/h3>\n\n\n\n<p>Antes de explicar a soluci\u00f3n ao problema da xeraci\u00f3n de audio usando outro n\u00facleo de procesamento, creo que \u00e9 importante explicar a diferenza entre crear distintos f\u00edos e utilizar a programaci\u00f3n as\u00edncrona en Godot.<\/p>\n\n\n\n<p>A programaci\u00f3n as\u00edncrona tam\u00e9n nos permite continuar executando o xogo mentres hai procesos longos en paralelo, como ler un arquivo. Deste xeito, no canto de esperar a que a lectura remate, o c\u00f3digo contin\u00faa executando outras funci\u00f3ns e retorna cando a lectura remata. Iso \u00e9 posible porque no caso da lectura dun arquivo \u00e9 o sistema operativo o que se encarga de xestionar ese proceso e logo notificar a Godot da s\u00faa finalizaci\u00f3n.<\/p>\n\n\n\n<p>A raz\u00f3n pola que non ser\u00eda unha soluci\u00f3n viable para este caso \u00e9 que a xeraci\u00f3n do audio tam\u00e9n ocorre dende o script de Godot. A programaci\u00f3n as\u00edncrona ocorre nun s\u00f3 f\u00edo de procesamento, polo que ao estar este ocupado na xeraci\u00f3n de audio non quedar\u00eda tempo a ning\u00fan outro procesamento.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Produtor\/Consumidor<\/h2>\n\n\n\n<p>Para recordar o problema, o que aqu\u00ed precisamos \u00e9 xerar audio para logo ser reproducido. A soluci\u00f3n \u00e9 mover a xeraci\u00f3n dese audio a un f\u00edo de procesamento distinto para que o sistema o poda executar nun n\u00facleo ocioso do noso procesador.<\/p>\n\n\n\n<p>Este tipo de problemas adoitan modelarse definindo o produtor da nosa informaci\u00f3n e o consumidor. Est\u00e1 claro cal \u00e9 cal neste sistema, pero hai detalles que debemos definir correctamente. O primeiro \u00e9 que o consumidor vai esperar a que o son estea finalizado, polo que necesitamos crealo por adiantado.<\/p>\n\n\n\n<p>Unha opci\u00f3n ser\u00eda crear un novo f\u00edo por cada audio que necesitemos e finalizalo cando estea listo. Non s\u00f3 ser\u00eda unha opci\u00f3n complexa de xestionar pero por riba comezar e finalizar f\u00edos de procesamento, por lixeiros que sexan, ten o seu custo asociado.<\/p>\n\n\n\n<p>Por isto, cando unha nova conversa comeza, crease un novo f\u00edo e, ao sa\u00edr, p\u00e9chase. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>var thread_prepare_audio: Thread\nvar producing = false\n\nfunc _ready():\n  thread_prepare_audio = Thread.new()\n  thread_prepare_audio.start(audio_prepare_func)\n\nfunc _exit_tree():\n  producing = false\n  thread_prepare_audio.wait_to_finish()\n\nfunc audio_prepare_func():\n  producing = true\n  while producing:\n    if !audio_generated:\n      # xeramos o audio\n    else:\n      OS.delay_msec(10)<\/code><\/pre>\n\n\n\n<p>Con este c\u00f3digo teriamos o suficiente para comezar o novo <em>thread<\/em> e pechalo de maneira adecuada nas funci\u00f3ns por defecto do script, <code>_ready<\/code> e <code>_exit_tree<\/code>. Pode parecer ineficiente que exista un bucle constante, pero ao &#8220;durmir&#8221; coa funci\u00f3n <code>delay_msec<\/code> sempre que non se necesite m\u00e1is audio o seu custo ser\u00e1 case imperceptible.<\/p>\n\n\n\n<p>Para que o consumidor espere a que exista ao menos un audio na lista, utilicei un <em><a href=\"https:\/\/docs.godotengine.org\/en\/stable\/classes\/class_semaphore.html\">sem\u00e1foro<\/a><\/em>. Este \u00e9 un tipo de obxecto que se pode utilizar de xeito seguro entre distintos f\u00edos e cont\u00e9n un n\u00famero que se pode aumentar con <code>post<\/code> e diminuir con <code>wait<\/code>. No caso de que o n\u00famero sexa 0 na chamada a <code>wait<\/code>, esta bloquear\u00e1 ata que se faga <code>post<\/code> dende outro <em>thread<\/em>. Co seguinte exemplo creo que se entende mellor:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>var consume_semaphore: Semaphore\n\nfunc audio_prepare_func():\n  producing = true\n  while producing:\n    if !audio_generated:\n      # xeramos audio\n      consume_semaphore.post()\n    else:\n      OS.delay_msec(10)\n\nfunc get_prepared_audio() -> AudioStreamWAV:\n  consume_semaphore.wait()\n  # collemos o audio producido\n  return audio<\/code><\/pre>\n\n\n\n<p>Deste xeito, cando se chame a <code>get_prepared_audio<\/code>, esta funci\u00f3n esperar\u00e1 a que audio_prepare_func te\u00f1a algo xerado e o notifique a trav\u00e9s do sem\u00e1foro.<\/p>\n\n\n\n<p>Xa vimos como se xera o son no artigo anterior as\u00ed que aqu\u00ed s\u00f3 me centrarei na comunicaci\u00f3n entre o produtor e o consumidor. Xa que a memoria de ambos f\u00edos \u00e9 a mesma, basta con ter unha lista que conte\u00f1a os audios xerados para envialos entre si. Temos que ter unha maneira de que esta lista non sexa accedida por m\u00e1is que un f\u00edo \u00e1 vez. O sem\u00e1foro s\u00f3 comproba que hai abondos audios, pero non impide que no mesmo momento se acceda \u00e1 lista tanto para engadir un novo como para coller outro.<\/p>\n\n\n\n<p>Os <em>mutex<\/em> son un tipo de sem\u00e1foro que s\u00f3 pode estar activado ou desactivado. O seu nome \u00e9 un acr\u00f3nimo do termo ingl\u00e9s  para exclusi\u00f3n mutua. E asegura que mentres est\u00e1 activo ningunha outra li\u00f1a de execuci\u00f3n poder\u00e1 entrar. Para o noso caso ser\u00e1 suficiente con protexer con <em>mutex<\/em> todos os accesos \u00e1 nosa lista de audios:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>var prepared_audio_list = &#91;]\nvar prepared_audio_mutex: Mutex\nvar thread_prepare_audio: Thread\nvar consume_semaphore: Semaphore\n\nfunc _ready():\n  thread_prepare_audio = Thread.new()\n  prepared_audio_mutex = Mutex.new()\n  consume_semaphore = Semaphore.new()\n  thread_prepare_audio.start(audio_prepare_func)\n\nfunc audio_prepare_func():\n  producing = true\n  var num_audios = 0\n  while producing:\n    prepared_audio_mutex.lock()\n    num_audios = prepared_audio_list.size()\n    prepared_audio_mutex.unlock()\n    if num_audios &lt; 3:\n      # xeramos audio\n      prepared_audio_mutex.lock()\n      prepared_audio_list.push_back(audio)\n      prepared_audio_mutex.unlock()\n      consume_semaphore.post()\n    else:\n      OS.delay_msec(10)\n\nfunc get_prepared_audio() -> AudioStreamWAV:\n  consume_semaphore.wait()\n  prepared_audio_mutex.lock()\n  audio = prepared_audio_list.pop_front()\n  prepared_audio_mutex.unlock()\n  return audio<\/code><\/pre>\n\n\n\n<p>Visto en conxunto, o c\u00f3digo pode parecer bastante complexo, pero o \u00fanico engadido agora for rodear de <code>lock<\/code> e <code>unlock<\/code> cada interacci\u00f3n coa lista de audios. Tam\u00e9n aqui se ve expl\u00edcito cando se xera un audio novo, neste caso cando hai menos de 3 elementos na lista. Isto \u00e9 totalmente arbitrario e pode ser tan baixo como 1.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusi\u00f3n<\/h2>\n\n\n\n<p>Utilizar varios n\u00facleos pode resultar complexo, pero creo que este artigo pode ser un bo punto de partida para intentar resolver outros moitos problemas con Godot.<\/p>\n\n\n\n<p>Quedoume algo m\u00e1is longo do que me gustar\u00eda, pero hab\u00eda bastantes detalles que explicar. De todos os xeitos, os comentarios est\u00e1n abertos para calquera d\u00fabida.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>O post anterior, sobre engadir voces aos personaxes en Godot quedou inconcluso, ao deixar para m\u00e1is adiante as optimizaci\u00f3ns necesarias para executalo nun m\u00f3bil de gama baixa. Xa dende a propia documentaci\u00f3n de GDXFR contraindican o uso do plugin como expresei no artigo anterior. A raz\u00f3n \u00e9 que, dados uns par\u00e1metros iniciais, a xeraci\u00f3n do [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":111,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":3,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"federated","footnotes":""},"categories":[1],"tags":[],"class_list":["post-110","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-sen-categoria"],"_links":{"self":[{"href":"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-json\/wp\/v2\/posts\/110","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-json\/wp\/v2\/comments?post=110"}],"version-history":[{"count":1,"href":"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-json\/wp\/v2\/posts\/110\/revisions"}],"predecessor-version":[{"id":112,"href":"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-json\/wp\/v2\/posts\/110\/revisions\/112"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-json\/wp\/v2\/media\/111"}],"wp:attachment":[{"href":"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-json\/wp\/v2\/media?parent=110"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-json\/wp\/v2\/categories?post=110"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.amarinha.gal\/metarcade_bitacora\/wp-json\/wp\/v2\/tags?post=110"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}