Skip to content

Commit 23490d8

Browse files
committed
feat(pipeline): enhance master pipeline with progress tracking and asset handling
Add support for streaming progress updates in the master pipeline, allowing real-time feedback during asset processing. Update the pipeline stages to ensure LOD0 is treated as the final deliverable, with automatic detection based on asset characteristics. Improve the handling of intermediate files and ensure that progress dashboards reflect all stages of the pipeline, enhancing visibility and user experience.
1 parent bba8c74 commit 23490d8

4 files changed

Lines changed: 53 additions & 3 deletions

File tree

AGENTS.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,9 @@ VibeGame has its own CI workflow in `VibeGame/.github/workflows/` (Bun + TypeScr
372372
- VibeGame: correções reutilizáveis devem ir para a **engine**; no **jogo/exemplo** (ex. `simple-rpg`) ficam só ajustes específicos desse jogo.
373373
- Exemplos como `simple-rpg`: pós-processamento e partículas com intensidade moderada para jogabilidade, mesmo quando os efeitos permanecem ativos para teste.
374374
- Áudio 3D e SFX de movimento: preferir integração via sistema da engine (XML/recipes, `AudioListener` + câmera principal) e alinhar o som à ação; evitar silêncio inicial longo nos WAV (trim na geração com `text2sound` ou na importação) para não somar latência perceptível.
375-
- Instalações e retries de ferramentas devem ser **não-interativos**: `install.sh`, instaladores por pacote e loops de correção não devem bloquear à espera de input (ex. prompts de licença, confirmações); quando a automação falha, reportar e seguir em vez de ficar preso num retry interativo.
375+
- Instalações e retries de ferramentas devem ser **não-interativos**: `install.sh`, instaladores por pacote e loops de correção não devem bloquear à espera de input (ex. prompts de licença, confirmações); quando a automação falha, reportar e seguir em vez de ficar preso num retry interativo. Em `install.sh --all` priorizar reuso de cache (pip/uv) e alinhamento de versões entre pacotes irmãos para reduzir downloads/builds redundantes.
376+
- Pipeline de game assets deve tratar o **LOD0 como o asset final entregável** com autodeteção do estágio terminal: se o asset tem animação, o GLB animado vira LOD0; se tem rig sem animação, o rigged vira LOD0; se só chega ao paint, o painted vira LOD0. LOD0 sem rig/animação quando o asset os tem é regressão.
377+
- CLIs de progresso e dashboards do `gameassets batch` devem refletir **todas as stages** do pipeline (LOD, rig, animate, validate) e não parar visualmente no paint quando há mais trabalho pela frente.
376378

377379
## Learned Workspace Facts
378380

@@ -392,7 +394,7 @@ VibeGame has its own CI workflow in `VibeGame/.github/workflows/` (Bun + TypeScr
392394

393395
- **Arquitetura de responsabilidades — mesh operations**: O **Text3D** é o único dono de operações de mesh (LOD, collision, simplify, remesh, remesh-textured, `topology-fix`, `bake-master`). O GameAssets NÃO deve conter código de mesh — apenas orquestra subprocessos `text3d`. Não usar `bpy` nem `trimesh` diretamente no GameAssets (o legado `bpy_simplify.py` foi removido). O `text3d lod` preserva armatures/animations — não é necessário um caminho separado para LOD rigged. Transferência de weights rigged HI → LODs é responsabilidade do `rigging3d transfer-weights` (não do Text3D).
394396

395-
- **Master pipeline (`gameassets batch --master-pipeline`)**: DAG canónico em `GameAssets/src/gameassets/pipeline_master.py` com stages numeradas: (1) `text3d generate` (shape cru), (2) `text3d topology-fix` (clean, inclui `--export-origin feet|center|none` e `--fill-holes-sides N`), (3) paint, (4) `text3d bake-master` (LOD0 com bake de normais + KTX2/meshopt opcionais a partir do high-poly clean), (5–7) LOD1/LOD2/collision, (8) `rigging3d transfer-weights --source HI --target LOD0 --target LOD1 ...`, (9) animate por LOD, (10) `gamedev-lab check glb --category ...`. Intermediários (`shape`, `painted`, `rigged_hi`, `clean`, `normal_map`) são movidos para `_intermediate/` no fim. `GameProfile` tem `master_pipeline`, `master_validate`, `master_bake_normals`; `--legacy-pipeline` força o pipeline antigo. Flag `text3d generate --no-topology-fix` mantém o Stage 1 cru.
397+
- **Master pipeline (`gameassets batch`, agora default)**: DAG canónico em `GameAssets/src/gameassets/pipeline_master.py` com stages numeradas: (1) `text3d generate` (shape cru), (2) `text3d topology-fix` (clean, inclui `--export-origin feet|center|none` e `--fill-holes-sides N`), (3) paint, (4) `text3d bake-master` (LOD0 com bake de normais + KTX2/meshopt opcionais a partir do high-poly clean), (5–7) LOD1/LOD2/collision, (8) `rigging3d transfer-weights --source HI --target LOD0 --target LOD1 ...`, (9) animate por LOD, (10) `gamedev-lab check glb --category ...`. Intermediários (`shape`, `painted`, `rigged_hi`, `clean`, `normal_map`) são movidos para `_intermediate/` no fim — o `resume` do batch tem de procurar nesses caminhos para não regenerar do zero quando os intermediários já foram arquivados. `GameProfile` tem `master_pipeline`, `master_validate`, `master_bake_normais`; `--legacy-pipeline` força o pipeline antigo. Flag `text3d generate --no-topology-fix` mantém o Stage 1 cru. A correção de orientação/origem Hunyuan3D → OpenGL (rotação que põe o modelo de pé) tem de ser propagada por **todas** as stages; regressão típica é o mesh aparecer "de barriga para cima" já a partir do `_shape`.
396398

397399
- **Validação GLB — `gamedev-lab check glb`**: usa `GameDevLab/src/gamedev_lab/glb_meta.py` (parser binário do GLB sem `bpy`) para extrair `attributes_present`, `extensions_used`, `texture_mime_types`, `v_per_tri`, `world_bounds_y_min`. Aceita `--category <lod0|lod1|lod2|rigged|collision>` (regras YAML em `GameAssets/src/gameassets/data/rules/*.yaml`) e `--no-bpy-inspect` para correr sem Blender. Regras suportam `mesh_totals.v_per_tri`, `attributes_required`, `texture_format`, `compression`, `origin.y_min`, `face_count.max_per_category`.
398400

GameAssets/src/gameassets/batch_cmd.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,7 @@ def _finalize_mesh_ok_d(
13931393
gpu_ids=gpu_ids,
13941394
with_lod=with_lod,
13951395
with_collision=with_collision,
1396+
on_progress_line=dash.feed_line,
13961397
):
13971398
failures += 1
13981399

@@ -1863,6 +1864,7 @@ def _finalize_mesh_ok_d(
18631864
gpu_ids=gpu_ids,
18641865
with_lod=with_lod,
18651866
with_collision=with_collision,
1867+
on_progress_line=dash.feed_line,
18661868
):
18671869
failures += 1
18681870
append_log(rec_d)
@@ -2398,6 +2400,7 @@ def _finalize_mesh_ok(
23982400
gpu_ids=gpu_ids,
23992401
with_lod=with_lod,
24002402
with_collision=with_collision,
2403+
on_progress_line=dash.feed_line,
24012404
):
24022405
failures += 1
24032406

@@ -2859,6 +2862,7 @@ def _finalize_mesh_ok(
28592862
gpu_ids=gpu_ids,
28602863
with_lod=with_lod,
28612864
with_collision=with_collision,
2865+
on_progress_line=dash.feed_line,
28622866
)
28632867
append_log(rec)
28642868
progress.advance(task2)
@@ -2922,6 +2926,7 @@ def _finalize_mesh_ok(
29222926
gpu_ids=gpu_ids,
29232927
with_lod=with_lod,
29242928
with_collision=with_collision,
2929+
on_progress_line=dash.feed_line,
29252930
):
29262931
failures += 1
29272932
append_log(rec)

GameAssets/src/gameassets/pipeline.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ def _post_text3d_mesh_extras(
568568
use_master_pipeline: bool | None = None,
569569
with_validate: bool | None = None,
570570
bake_normals: bool | None = None,
571+
on_progress_line: Any = None,
571572
) -> bool:
572573
"""Define mesh_path, part3d, rigging3d, animator3d. Devolve True se algum passo falhou.
573574
@@ -599,6 +600,7 @@ def _post_text3d_mesh_extras(
599600
with_animate=with_animate and (animator3d_bin is not None),
600601
with_validate=with_validate,
601602
bake_normals=bake_normals,
603+
on_progress_line=on_progress_line,
602604
)
603605
aggregate_master_results(mres.stages, rec)
604606
if mres.lod0_path and mres.lod0_path.is_file():

GameAssets/src/gameassets/pipeline_master.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from __future__ import annotations
1919

2020
import logging
21+
from collections.abc import Callable
2122
from dataclasses import dataclass, field
2223
from pathlib import Path
2324
from typing import Any
@@ -45,6 +46,11 @@
4546
from .profile import GameProfile
4647
from .runner import merge_subprocess_output, resolve_binary, run_cmd
4748

49+
try:
50+
from gamedev_shared.subprocess_utils import run_cmd_streaming as _run_cmd_streaming
51+
except ImportError: # pragma: no cover
52+
_run_cmd_streaming = None # type: ignore[assignment]
53+
4854
log = logging.getLogger(__name__)
4955

5056

@@ -118,12 +124,19 @@ def _stage(
118124
*,
119125
item_id: str | None = None,
120126
profile_enabled: bool = False,
127+
on_progress_line: "Callable[[str], None] | None" = None,
121128
) -> StageResult:
122129
"""Executa um stage do master pipeline.
123130
124131
Round 2: envolto em ``ProfilerSession`` para spans no perf.db quando
125132
``profile_enabled`` (controlado por ``GAMEDEV_PROFILE`` no child_env).
126133
Emite ``emit_progress`` no início e fim para visibilidade no dashboard.
134+
135+
``on_progress_line``: callback alimentado linha-a-linha com stdout do
136+
subprocesso. Permite encaminhar ``emit_progress`` events emitidos pelas
137+
ferramentas (text3d, rigging3d, animator3d) para o dashboard do
138+
gameassets — sem isso, o dashboard só vê os events do orquestrador
139+
(start/end por stage) e parece "congelar" após paint3d.
127140
"""
128141
import time as _time
129142

@@ -141,7 +154,31 @@ def _stage(
141154
cli_profile=profile_enabled,
142155
params={"item_id": item_id} if item_id else None,
143156
):
144-
r = run_cmd(argv, extra_env=env, cwd=cwd)
157+
if on_progress_line is not None and _run_cmd_streaming is not None:
158+
# Stream stdout para callback (dashboard) E acumula resultado.
159+
stdout_buf: list[str] = []
160+
stderr_buf: list[str] = []
161+
162+
def _on_out(line: str) -> None:
163+
stdout_buf.append(line)
164+
try:
165+
on_progress_line(line)
166+
except Exception: # noqa: BLE001
167+
pass
168+
169+
def _on_err(line: str) -> None:
170+
stderr_buf.append(line)
171+
172+
rs = _run_cmd_streaming(
173+
argv,
174+
on_stdout_line=_on_out,
175+
on_stderr_line=_on_err,
176+
cwd=cwd,
177+
extra_env=env,
178+
)
179+
r = rs
180+
else:
181+
r = run_cmd(argv, extra_env=env, cwd=cwd)
145182
except Exception as exc: # noqa: BLE001
146183
dt = _time.perf_counter() - t0
147184
if item_id:
@@ -176,6 +213,7 @@ def run_master_pipeline(
176213
with_animate: bool = False,
177214
with_validate: bool = True,
178215
bake_normals: bool | None = None,
216+
on_progress_line: Callable[[str], None] | None = None,
179217
) -> MasterPipelineResult:
180218
"""Executa o DAG novo a partir de ``id_shape.glb`` e ``id_painted.glb``.
181219
@@ -213,6 +251,7 @@ def _run(name: str, argv: list[str], output: Path | None = None) -> StageResult:
213251
output,
214252
item_id=row.id,
215253
profile_enabled=profile_enabled,
254+
on_progress_line=on_progress_line,
216255
)
217256

218257
text3d_bin = _bin_or_none("TEXT3D_BIN", "text3d")
@@ -512,6 +551,7 @@ def resume_master_pipeline(
512551
with_animate: bool = False,
513552
with_validate: bool = True,
514553
bake_normals: bool | None = None,
554+
on_progress_line: Callable[[str], None] | None = None,
515555
) -> MasterPipelineResult:
516556
"""Retoma o master pipeline a partir do checkpoint detectado.
517557
@@ -564,6 +604,7 @@ def resume_master_pipeline(
564604
with_animate=with_animate,
565605
with_validate=with_validate,
566606
bake_normals=bake_normals,
607+
on_progress_line=on_progress_line,
567608
)
568609

569610

0 commit comments

Comments
 (0)