[PERF] Async Background Work Lanes for Assets and FS
Some checks failed
Intrepid/Prometeu/Runtime/pipeline/head There was a failure building this commit
Intrepid/Prometeu/Runtime/pipeline/pr-master This commit looks good

This commit is contained in:
bQUARKz 2026-06-28 09:35:08 +01:00
parent bd147938a0
commit 8f37d971cd
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
10 changed files with 1591 additions and 458 deletions

View File

@ -1,4 +1,4 @@
{"type":"meta","next_id":{"DSC":43,"AGD":44,"DEC":34,"PLN":123,"LSN":50,"CLSN":1}} {"type":"meta","next_id":{"DSC":43,"AGD":44,"DEC":35,"PLN":129,"LSN":50,"CLSN":1}}
{"type":"discussion","id":"DSC-0039","status":"abandoned","ticket":"render-pipeline-family-and-future-3d","title":"Render Pipeline Family and Future 3D","created_at":"2026-06-04","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","architecture","pipeline"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-render-pipeline-family-and-future-3d.md","status":"abandoned","created_at":"2026-06-04","updated_at":"2026-06-04","_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."} {"type":"discussion","id":"DSC-0039","status":"abandoned","ticket":"render-pipeline-family-and-future-3d","title":"Render Pipeline Family and Future 3D","created_at":"2026-06-04","updated_at":"2026-06-04","tags":["gfx","renderer","runtime","architecture","pipeline"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-render-pipeline-family-and-future-3d.md","status":"abandoned","created_at":"2026-06-04","updated_at":"2026-06-04","_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to close this agenda without a new decision because DSC-0038 already established enough architecture for future extension, and 3D is intentionally deferred."}
{"type":"discussion","id":"DSC-0040","status":"done","ticket":"vm-render-parallel-execution-boundary","title":"VM and Render Parallel Execution Boundary","created_at":"2026-06-04","updated_at":"2026-06-06","tags":["runtime","renderer","vm","concurrency","architecture","perf"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0048","file":"discussion/lessons/DSC-0040-vm-render-parallel-execution-boundary/LSN-0048-render-workers-need-a-closed-packet-contract.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-06"}]} {"type":"discussion","id":"DSC-0040","status":"done","ticket":"vm-render-parallel-execution-boundary","title":"VM and Render Parallel Execution Boundary","created_at":"2026-06-04","updated_at":"2026-06-06","tags":["runtime","renderer","vm","concurrency","architecture","perf"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0048","file":"discussion/lessons/DSC-0040-vm-render-parallel-execution-boundary/LSN-0048-render-workers-need-a-closed-packet-contract.md","status":"done","created_at":"2026-06-06","updated_at":"2026-06-06"}]}
{"type":"discussion","id":"DSC-0041","status":"open","ticket":"foreground-stack-game-pause-shell-vm-backed","title":"Foreground Stack, Game Pause, and VM-Backed Shell Coexistence","created_at":"2026-06-05","updated_at":"2026-06-05","tags":["runtime","os","lifecycle","shell","game","vm","foreground","architecture"],"agendas":[{"id":"AGD-0041","file":"AGD-0041-foreground-stack-game-pause-shell-vm-backed.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0041","status":"open","ticket":"foreground-stack-game-pause-shell-vm-backed","title":"Foreground Stack, Game Pause, and VM-Backed Shell Coexistence","created_at":"2026-06-05","updated_at":"2026-06-05","tags":["runtime","os","lifecycle","shell","game","vm","foreground","architecture"],"agendas":[{"id":"AGD-0041","file":"AGD-0041-foreground-stack-game-pause-shell-vm-backed.md","status":"open","created_at":"2026-06-05","updated_at":"2026-06-05"}],"decisions":[],"plans":[],"lessons":[]}
@ -18,9 +18,9 @@
{"type":"discussion","id":"DSC-0006","status":"open","ticket":"vm-owned-random-service","title":"Agenda - VM-Owned Random Service","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0005","file":"AGD-0005-vm-owned-random-service.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0006","status":"open","ticket":"vm-owned-random-service","title":"Agenda - VM-Owned Random Service","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0005","file":"AGD-0005-vm-owned-random-service.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0007","status":"open","ticket":"app-home-filesystem-surface-and-semantics","title":"Agenda - App Home Filesystem Surface and Semantics","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0006","file":"AGD-0006-app-home-filesystem-surface-and-semantics.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0007","status":"open","ticket":"app-home-filesystem-surface-and-semantics","title":"Agenda - App Home Filesystem Surface and Semantics","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0006","file":"AGD-0006-app-home-filesystem-surface-and-semantics.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0008","status":"done","ticket":"perf-runtime-telemetry-hot-path","title":"Agenda - [PERF] Runtime Telemetry Hot Path","created_at":"2026-03-27","updated_at":"2026-06-04","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0026","file":"discussion/lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0008","status":"done","ticket":"perf-runtime-telemetry-hot-path","title":"Agenda - [PERF] Runtime Telemetry Hot Path","created_at":"2026-03-27","updated_at":"2026-06-04","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0026","file":"discussion/lessons/DSC-0008-perf-runtime-telemetry-hot-path/LSN-0026-push-based-telemetry-model.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
{"type":"discussion","id":"DSC-0009","status":"open","ticket":"perf-async-background-work-lanes-for-assets-and-fs","title":"Agenda - [PERF] Async Background Work Lanes for Assets and FS","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0008","file":"AGD-0008-perf-async-background-work-lanes-for-assets-and-fs.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0009","status":"in_progress","ticket":"perf-async-background-work-lanes-for-assets-and-fs","title":"Agenda - [PERF] Async Background Work Lanes for Assets and FS","created_at":"2026-03-27","updated_at":"2026-06-28","tags":["perf","asset","fs","async","scheduler","runtime"],"agendas":[{"id":"AGD-0008","file":"AGD-0008-perf-async-background-work-lanes-for-assets-and-fs.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-06-28"}],"decisions":[{"id":"DEC-0034","file":"DEC-0034-async-work-lane-and-asset-backlog-contract.md","status":"accepted","created_at":"2026-06-28","updated_at":"2026-06-28","ref_agenda":"AGD-0008"}],"plans":[{"id":"PLN-0123","file":"PLN-0123-async-work-lane-specification-propagation.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0124","file":"PLN-0124-async-work-lane-runtime-infrastructure.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0125","file":"PLN-0125-asset-backlog-and-stable-slot-handles.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0126","file":"PLN-0126-asset-backlog-public-api-and-status-surface.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0127","file":"PLN-0127-async-lane-memcard-and-fs-integration-boundaries.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]},{"id":"PLN-0128","file":"PLN-0128-async-asset-pipeline-contract-tests-and-telemetry.md","status":"open","created_at":"2026-06-28","updated_at":"2026-06-28","ref_decisions":["DEC-0034"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0010","status":"done","ticket":"perf-host-desktop-frame-pacing-and-presentation","title":"Agenda - [PERF] Host Desktop Frame Pacing and Presentation","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0036","file":"discussion/lessons/DSC-0010-perf-host-desktop-frame-pacing-and-presentation/LSN-0036-frame-publication-and-host-invalidation-must-be-separate.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20"}]} {"type":"discussion","id":"DSC-0010","status":"done","ticket":"perf-host-desktop-frame-pacing-and-presentation","title":"Agenda - [PERF] Host Desktop Frame Pacing and Presentation","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0036","file":"discussion/lessons/DSC-0010-perf-host-desktop-frame-pacing-and-presentation/LSN-0036-frame-publication-and-host-invalidation-must-be-separate.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20"}]}
{"type":"discussion","id":"DSC-0011","status":"open","ticket":"perf-gfx-render-pipeline-and-dirty-regions","title":"Agenda - [PERF] GFX Render Pipeline and Dirty Regions","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0010","file":"AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0011","status":"abandoned","ticket":"perf-gfx-render-pipeline-and-dirty-regions","title":"Agenda - [PERF] GFX Render Pipeline and Dirty Regions","created_at":"2026-03-27","updated_at":"2026-06-28","tags":[],"agendas":[{"id":"AGD-0010","file":"AGD-0010-perf-gfx-render-pipeline-and-dirty-regions.md","status":"abandoned","created_at":"2026-03-27","updated_at":"2026-06-28","_override_reason":"User explicitly chose to remove this agenda because it no longer applies to the current render scenario; future optimizations can be discussed when needed."}],"decisions":[],"plans":[],"lessons":[],"_override_reason":"User explicitly chose to abandon this discussion because it no longer applies to the current render scenario; future optimizations can be discussed when needed."}
{"type":"discussion","id":"DSC-0012","status":"done","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-04-19","tags":["perf","runtime","syscall","telemetry","debug","asset"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0034","file":"discussion/lessons/DSC-0012-perf-runtime-introspection-syscalls/LSN-0034-host-owned-debug-boundaries.md","status":"done","created_at":"2026-04-19","updated_at":"2026-04-19"}]} {"type":"discussion","id":"DSC-0012","status":"done","ticket":"perf-runtime-introspection-syscalls","title":"Agenda - [PERF] Runtime Introspection Syscalls","created_at":"2026-03-27","updated_at":"2026-04-19","tags":["perf","runtime","syscall","telemetry","debug","asset"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0034","file":"discussion/lessons/DSC-0012-perf-runtime-introspection-syscalls/LSN-0034-host-owned-debug-boundaries.md","status":"done","created_at":"2026-04-19","updated_at":"2026-04-19"}]}
{"type":"discussion","id":"DSC-0013","status":"done","ticket":"perf-host-debug-overlay-isolation","title":"Agenda - [PERF] Host Debug Overlay Isolation","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0027","file":"discussion/lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0013","status":"done","ticket":"perf-host-debug-overlay-isolation","title":"Agenda - [PERF] Host Debug Overlay Isolation","created_at":"2026-03-27","updated_at":"2026-04-10","tags":[],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0027","file":"discussion/lessons/DSC-0013-perf-host-debug-overlay-isolation/LSN-0027-host-debug-overlay-isolation.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
{"type":"discussion","id":"DSC-0024","status":"done","ticket":"generic-memory-bank-slot-contract","title":"Agenda - Generic Memory Bank Slot Contract","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["runtime","asset","memory-bank","slots","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0029","file":"discussion/lessons/DSC-0024-generic-memory-bank-slot-contract/LSN-0029-slot-first-bank-telemetry-belongs-in-asset-manager.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0024","status":"done","ticket":"generic-memory-bank-slot-contract","title":"Agenda - Generic Memory Bank Slot Contract","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["runtime","asset","memory-bank","slots","host"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0029","file":"discussion/lessons/DSC-0024-generic-memory-bank-slot-contract/LSN-0029-slot-first-bank-telemetry-belongs-in-asset-manager.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}

View File

@ -2,84 +2,556 @@
id: AGD-0008 id: AGD-0008
ticket: perf-async-background-work-lanes-for-assets-and-fs ticket: perf-async-background-work-lanes-for-assets-and-fs
title: Agenda - [PERF] Async Background Work Lanes for Assets and FS title: Agenda - [PERF] Async Background Work Lanes for Assets and FS
status: open status: accepted
created: 2026-03-27 created: 2026-03-27
resolved: resolved: 2026-06-28
decision: decision: DEC-0034
tags: [] tags: [perf, asset, fs, async, scheduler, runtime]
--- ---
# Agenda - [PERF] Async Background Work Lanes for Assets and FS # Agenda - [PERF] Async Background Work Lanes for Assets and FS
## Contexto
Depois das decisoes de render worker, a concorrencia de render saiu do escopo
desta agenda. O problema restante e a lane de trabalho assincrono para IO,
decode e persistencia.
O modelo de execucao que queremos discutir agora assume uma terceira core/lane
dedicada a trabalhos assincronos de runtime:
```text
Core/lane 1: VM, firmware, SystemOS e frame logico
Core/lane 2: render worker / rasterizacao assincrona
Core/lane 3: async work lane para asset IO/decode e possivelmente FS
```
Essa terceira lane nao deve virar um pool grande e solto. A intencao e ter uma
fila previsivel, com capacidade limitada, telemetria e regras de saturacao.
Para assets, a necessidade e concreta: `asset.load()` ainda cria uma thread do
SO por requisicao. Para FS, a motivacao provavel era evitar que operacoes de
leitura/escrita/listagem/delete, hoje chamadas de forma sincrona pelo dispatch,
roubem tempo do frame quando o backend real envolver IO. Precisamos verificar
se FS deve usar a mesma lane agora ou apenas reservar compatibilidade.
## Problema ## Problema
`asset.load()` hoje cria uma thread do SO por requisicao. Ao mesmo tempo, `fs` ainda nao tem uma politica clara para sync assincrono barato em hardware simples. `asset.load()` hoje dispara `thread::spawn` por requisicao de asset nao
residente. Isso escala mal, cria jitter e deixa o runtime sem uma politica
unica de backpressure.
O projeto precisa de paralelismo para IO/decode/sync, mas o target inclui handheld DIY e hardware barato, onde um pool grande ou explosao de threads pode piorar a latencia em vez de melhorar. O dominio `fs` tambem tem operacoes potencialmente bloqueantes (`read`,
`write`, `list_dir`, `delete`, memcard commit/read/write) e a tabela de
syscalls ja as marca como nao deterministicas/custosas. Ainda assim, a
implementacao atual executa essas chamadas diretamente no caminho de dispatch.
## Dor Sem um contrato explicito, cada dominio pode inventar sua propria concorrencia:
asset criando threads, FS bloqueando o frame, e memcard herdando comportamento
acidental do backend.
## Pontos Criticos
- `thread::spawn` por request escala mal e cria jitter. - `thread::spawn` por request escala mal e cria jitter.
- assets e `fs` competem por IO sem uma politica unica de fila/prioridade. - A terceira core/lane deve ser um recurso de plataforma controlado, nao uma
- sem lane dedicada, operacoes de background tendem a vazar custo para o main loop. permissao geral para criar threads.
- sem disciplina, o host desktop vira referencia errada para hardware fraco. - Asset loading precisa de um pool/fila limitada para multiplos loads
pendentes.
- FS pode justificar a mesma lane quando envolver IO real, mas nao devemos
assinar uma API assincrona ampla sem entender a semantica de retorno.
- Commit/install em bancos residentes pode ter restricoes de determinismo e
ownership; decode pode ser assincrono, mas publicacao/instalacao talvez tenha
que cruzar de volta para a lane principal.
- A estrategia precisa funcionar em hardware alvo com poucos cores e tambem no
host desktop sem transformar desktop em referencia de custo errada.
- Render worker ja tem seu proprio contrato e nao deve compartilhar essa lane.
## Hotspots Atuais ## Hotspots Atuais
- [asset.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-drivers/src/asset.rs#L353) - [asset.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-drivers/src/asset.rs#L438) - `AssetManager::load`.
- [tick.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs#L53) - [asset.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-drivers/src/asset.rs#L507) - `thread::spawn` por load.
- [runner.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/host/prometeu-host-desktop-winit/src/runner.rs#L315) - [fs.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-hal/src/syscalls/domains/fs.rs#L4) - `fs` e `mem` marcados como superficies custosas/nao deterministicas.
- [dispatch.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs#L549) - chamadas `fs.*` executam no dispatch.
- [virtual_fs.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-system/src/services/fs/virtual_fs.rs#L69) - VFS normaliza path e chama backend diretamente.
## Alvo da Discussao ## Alvo da Discussao
Fechar um modelo de execucao assincrona para `asset` e `fs` que seja previsivel em hardware simples. Fechar um modelo de terceira core/lane para trabalho assincrono de runtime,
comecando por asset loading e avaliando se FS deve entrar na mesma lane no v1.
O alvo nao e criar multitarefa geral, callbacks para guest, ou um scheduler
paralelo da VM. A lane deve executar trabalho de IO/decode/sync e publicar
resultados observaveis por status/polling em pontos previsiveis.
## O Que Precisa Ser Definido ## O Que Precisa Ser Definido
1. Topologia de workers. 1. Topologia da terceira lane.
Escolher entre: Definir:
- uma thread dedicada para `asset` e outra para `fs`; - uma async work lane dedicada, separada da VM e do render worker;
- um worker unico multiplexando filas; - se a lane e uma thread fixa no host e uma core dedicada no hardware alvo;
- um pool minimo e fixo; - fallback quando nao houver core fisico dedicado;
- proibicao explicita de `spawn` por request. - proibicao explicita de `thread::spawn` por request no caminho normal.
2. Separacao por dominio. 2. Pool/fila de asset loading.
Decidir se `asset` e `fs` compartilham scheduler/fila ou se cada dominio tem lane propria. Definir:
- backlog de pedidos sem limite operacional fixo;
- exatamente um job ativo por vez na lane;
- o "pool" significa backlog e estados de job, nao multiplos workers reais;
- politica de substituicao/cancelamento para pedidos que miram o mesmo
bank/slot;
- status para pending/backlog, ativo, progresso, cancelamento, erro de
decode e sucesso.
3. Politica de prioridade. 3. Fases do asset load.
Separar:
- request e alocacao de handle;
- leitura de bytes;
- decode/materializacao;
- staging de resultado;
- install/commit em banco residente.
Ponto a fechar: quais fases rodam na terceira lane e quais voltam para a
lane principal por determinismo/ownership.
4. Inclusao de FS na mesma lane.
Verificar se `fs` deve usar a terceira lane no v1 para:
- leituras grandes;
- escritas;
- listagem;
- delete;
- memcard commit/read/write.
Ponto a fechar: FS sync publico continua bloqueante/status-first, vira
request/poll, ou ganha apenas uma implementacao interna que drena trabalho em
pontos de frame.
5. Separacao por dominio.
Decidir se `asset` e `fs` compartilham uma fila com classes de prioridade ou
se a terceira lane contem subfilas dedicadas.
6. Politica de prioridade.
Definir: Definir:
- prioridade de loads visuais vs audio; - prioridade de loads visuais vs audio;
- prioridade de sync de save/config; - prioridade de save/config;
- limite de trabalho por frame para install/commit. - prioridade de memcard commit;
- starvation prevention;
- limite de trabalho de instalacao/publicacao por frame.
4. Modelo de retorno. 7. Modelo de retorno e telemetria.
Fechar como o guest observa backlog, cancelamento e saturacao: Fechar como guest/runtime observa:
- status imediato de fila cheia; - `pending`;
- status de pending; - `loading`;
- metrica de backlog; - `ready`;
- politica de cancelamento. - `canceled`;
- `backend_unavailable`;
- backlog;
- posicao de um asset no backlog;
- progresso percentual do asset ativo;
- drops/retries quando aplicavel.
5. Orçamento para hardware barato. 8. Orcamento para hardware barato.
Definir quantas threads o runtime pode assumir como baseline. Definir o baseline:
- VM principal;
- render worker;
- terceira async work lane;
- limites de memoria para jobs pendentes e buffers temporarios.
## Open Questions de Arquitetura ## Opcoes
1. O v1 precisa de lanes separadas para `asset` e `fs` ou basta uma fila central com classes de prioridade? ### Opcao A - Terceira lane unica com subfilas e pool limitado de assets
2. Decode de asset fica no mesmo worker do IO ou em fase distinta?
3. Commit/install no device continua no main thread por determinismo? - **Abordagem:** criar uma async work lane dedicada. Assets usam um pool/fila
de jobs nessa lane; FS tambem pode submeter jobs na mesma lane, com
prioridade menor ou classe separada.
- **Pro:** modelo simples de budget; evita explosao de threads; encaixa no alvo
de terceira core; permite compartilhar backpressure e telemetria.
- **Contra:** assets e FS podem competir por IO; precisa politica clara para
evitar que save/config fique atras de loads longos.
- **Manutenibilidade:** boa se a lane tiver tipos de job e estados explicitos.
### Opcao B - Lane de assets agora, FS apenas reservado
- **Abordagem:** fechar v1 somente para asset loading, removendo `spawn` por
request. FS continua sincrono por enquanto, mas o contrato da lane reserva
compatibilidade para FS no futuro.
- **Pro:** menor escopo; resolve a dor concreta de assets rapidamente; evita
alterar semantica de FS antes da agenda de app home filesystem.
- **Contra:** nao resolve stalls reais de FS/memcard; pode exigir refatorar a
lane depois para aceitar FS.
- **Manutenibilidade:** boa no curto prazo, com risco de segunda migracao.
### Opcao C - Lanes separadas para asset e FS
- **Abordagem:** criar uma lane/pool para assets e outra para FS.
- **Pro:** isolamento forte; FS/save nao compete diretamente com loads de
textura/audio/cena.
- **Contra:** consome mais threads/cores; contraria a premissa de terceira core
unica; pior para hardware simples.
- **Manutenibilidade:** media; isolamento ajuda, mas a topologia fica pesada.
### Opcao D - Pool global pequeno sem nocao de terceira lane
- **Abordagem:** trocar `spawn` por request por um pool global fixo.
- **Pro:** simples de implementar em host desktop.
- **Contra:** perde a arquitetura de lanes por core; pode mascarar custo em
desktop; fica menos claro como mapear para hardware alvo.
- **Manutenibilidade:** baixa para o modelo de console se virar dependencia
acidental de scheduler do SO.
## Sugestao / Recomendacao
Recomendo seguir com a Opcao A como direcao de arquitetura, mas fechar a
decisao em duas camadas:
1. Contrato obrigatorio agora:
- terceira async work lane como recurso runtime;
- assets deixam de usar `thread::spawn` por requisicao;
- asset loading passa por backlog consultavel/reordenavel;
- status, progresso e telemetria de backlog ficam explicitos.
2. Contrato condicionado para FS:
- FS deve ser analisado como consumidor legitimo da mesma lane porque suas
operacoes podem envolver IO real e ja sao custosas/nao deterministicas;
- a decisao precisa escolher entre incluir FS no v1 ou reservar a lane para
FS sem mudar ainda a superficie publica;
- memcard precisa ser considerado junto com FS, porque usa a mesma
capacidade e backend de persistencia.
O ponto importante: a terceira core/lane deve ser a unidade de concorrencia.
O pool de assets deve existir dentro desse modelo, nao como criacao livre de
threads por load.
## Respostas Consolidadas Ate Aqui
1. O pool de assets deve ser uma lane serial.
- Ha exatamente um job ativo na async work lane.
- O pool representa backlog, estados e controle de ordem, nao multiplos
workers executando em paralelo.
2. O backlog de pedidos deve ser semanticamente ilimitado.
- Nao existe `queue_full` como status normal do dominio.
- Como cada pedido mira um `bank_type/slot` e pedidos novos para o mesmo
alvo substituem os anteriores, o backlog efetivo fica limitado pela soma
dos slots dos bancos.
- O contrato nao precisa expor limite de fila, mas a implementacao deve
garantir que metadados de handles cancelados/superseded nao crescam sem
coleta.
3. Asset loading precisa expor consulta e reordenacao do backlog.
- Deve existir API publica/runtime para consultar onde um asset esta no
backlog.
- Deve existir capacidade de adiantar ou atrasar um pedido de asset na fila.
- O asset ativo deve expor progresso percentual.
- Requests novos para o mesmo `bank_type/slot` devem cancelar pedidos
anteriores pendentes para aquele alvo, evitando carregar asset que ja foi
substituido antes de executar.
- Se o alvo ja estiver carregado com o mesmo `asset_id`, o novo request nao
entra no backlog; ele retorna imediatamente pronto com handle disponivel.
- Se um novo request substituir o alvo atualmente ativo, a lane deve cancelar
em ponto cooperativo quando possivel; se o trabalho nao puder ser
interrompido, o resultado antigo deve ser descartado ao final por geracao
superseded.
- `superseded` significa que um request foi substituido por outro request
mais novo para o mesmo `bank_type/slot`. Nao e erro e nao e cancelamento
manual; e obsolescencia operacional do pedido antigo.
- Handles representam o slot do bank como alvo estavel. Dentro do estado do
handle deve existir uma secao de request/backlog para aquele slot,
informando se ha pedido pendente/ativo, qual asset foi solicitado,
progresso, posicao e geracao.
- Handles devem ser capazes de consultar o estado do alvo `bank_type/slot`
a qualquer momento. Se nao houver asset valido carregado e tambem nao
houver request pendente para aquele alvo, a consulta deve retornar estado
explicito de vazio/invalido, nao falhar por ausencia de request ativo.
4. Decode/materializacao fria rodam na terceira lane.
- Leitura e decode de asset podem acontecer fora da lane principal.
- O resultado deve voltar como material pronto/staged.
5. Install/commit em bancos residentes continua na lane principal.
- A async lane prepara trabalho.
- A lane principal publica/instala em pontos previsiveis por ownership,
determinismo e integracao com bancos/render/audio.
6. FS e memcard entram como consumidores possiveis da mesma lane, mas precisam
ser melhor amarrados com a agenda de app home filesystem.
- A `DSC-0007` / `AGD-0006` deve ser ajustada para estabelecer melhor a
superficie publica e o modelo async/sync de FS.
- Esta agenda deve afirmar a existencia da lane para trabalhos async de IO
desse tipo e permitir que FS a consuma.
- Questoes de API publica de FS, request/poll e semantica de app home devem
ficar a cargo da agenda 6.
- FS, memcard e assets nao devem operar sobrepostos quando isso gerar
concorrencia real de backend; se houver competicao, a fila unica resolve a
ordem.
7. Prioridade quando houver competicao:
- memcard commit/write;
- FS write/config;
- asset visual/audio load;
- list/read nao critico.
8. Status-first continua valendo.
- `backend_unavailable`, `canceled`, erro de decode e estados de job sao
status operacionais.
- Cancelamento por superseding de pedido no mesmo bank/slot e resultado
operacional normal, nao fault.
- Nao ha `queue_full` no contrato esperado.
- Trap permanece para violacao estrutural, como argumento invalido,
capability ausente ou handle estruturalmente invalido.
9. Telemetria minima:
- backlog atual;
- posicao por asset;
- progresso percentual do asset ativo;
- jobs submetidos, concluidos, falhos e cancelados;
- tempo por job;
- percentis por asset enquanto estiver carregando na lane.
10. Escopo da agenda.
- O foco desta agenda e a lane, assets e memcard/game persistence.
- FS entra como consumidor permitido da lane de IO, mas as decisoes de API
publica de FS pertencem a `AGD-0006`.
11. API de backlog.
- A API inicial sugerida cobre os casos esperados.
- `asset.backlog_promote(handle)` deve ser mantido como atalho oficial para
`asset.backlog_move(handle, 1)`.
- `asset.backlog_demote(handle)` deve ser mantido como atalho oficial para
mover o pedido para o fim dos pendentes.
- Os atalhos nao criam semantica nova; apenas evitam que o caller calcule
posicoes comuns.
- O handle deve permitir consulta estavel do slot do bank mesmo quando nao ha
asset valido carregado.
- O estado do handle deve separar:
- estado do slot/residencia atual;
- estado do request/backlog atual para aquele slot.
- Operacoes que mudam estado, como commit/cancel/reorder, atuam sobre a
secao de request/backlog do handle e precisam validar geracao/request atual
para nao agir sobre um pedido substituido.
12. Cancelamento por substituicao.
- Quando um request novo chega para o mesmo `bank_type/slot`, o request
anterior e marcado imediatamente como `superseded`.
- Se o request anterior ainda estiver no backlog, ele sai da fila.
- Se o request anterior estiver ativo, a lane tenta cancelar
cooperativamente quando a fase atual permitir.
- Se a fase ativa nao puder ser interrompida com baixo custo, ela termina e
o resultado e descartado ao final porque a geracao do alvo ja mudou.
- Como o handle pertence ao slot do bank, ele continua consultavel; sua
secao de request deve indicar que a geracao anterior foi `superseded` ou
que nao ha request ativo para aquele alvo.
13. Fases de progresso.
- `read/decode/stage` e suficiente como modelo minimo para `GLYPH`, `SOUND`
e `SCENE`.
- Subfases futuras podem detalhar progresso dentro de `decode` sem mudar o
contrato geral.
14. Percentis.
- Percentis devem existir por `bank_type` como telemetria principal.
- Percentis por `asset_id` tambem sao uteis, mas podem usar janela pequena
ou agregador leve.
- Percentis sao atualizados no fechamento do job, nao durante o hot path.
15. Ajuste esperado na agenda de FS.
- `AGD-0006` deve registrar que existe uma async IO lane compartilhavel por
FS/memcard/assets.
- Esta agenda so decide que FS pode consumir a lane.
- A API publica de FS, incluindo request/poll ou sync aparente, pertence a
`AGD-0006`.
## API Inicial Sugerida Para Assets
Esta agenda ainda nao fecha nomes finais de ABI, mas a direcao inicial sugerida
e manter `asset.load/status/commit/cancel` e adicionar uma superficie pequena
para backlog:
1. `asset.backlog_info() -> (status, pending_count, active_handle, active_asset_id, active_bank_type, active_slot, active_progress)`
- Consulta o estado geral da lane de assets.
- `active_progress` usa escala inteira, preferencialmente `0..10000` para
permitir duas casas decimais sem float.
2. `asset.backlog_position(handle) -> (status, state, position, progress)`
- Consulta um pedido especifico.
- `position = 0` significa job ativo.
- `position > 0` significa posicao no backlog.
- `position = -1` pode representar nao enfileirado/pronto/cancelado,
dependendo do `state`.
3. `asset.backlog_move(handle, new_position) -> status`
- Move um pedido pendente para uma posicao absoluta.
- `new_position = 1` significa primeiro pendente depois do ativo.
- Nao interrompe diretamente o job ativo; substituicao do mesmo
`bank_type/slot` usa a regra de superseding.
4. `asset.backlog_promote(handle) -> status`
- Atalho para mover o pedido para a frente do backlog pendente.
5. `asset.backlog_demote(handle) -> status`
- Atalho para mover o pedido para o fim do backlog pendente.
6. `asset.target_status(bank_type, slot) -> (status, asset_id, handle, state, position, progress)`
- Consulta por alvo operacional, que e o que realmente controla superseding.
- Ajuda quando o caller quer saber "o que esta programado para este slot".
Estados sugeridos para `state`:
- `empty`;
- `invalid`;
- `queued`;
- `active`;
- `ready`;
- `committed`;
- `canceled`;
- `superseded`;
- `error`;
- `backend_unavailable`.
Regra de design: APIs de backlog sao operacionais e status-first. Reordenar um
handle inexistente, ja concluido ou ja superseded retorna status operacional
apropriado quando o handle e conhecido; violações estruturais continuam Trap.
Handles sao observaveis e representam slots de banks. Um handle conhecido pode
ser consultado mesmo quando o slot alvo nao contem asset valido carregado; nesse
caso a consulta deve retornar estado como `empty` ou `invalid`.
O estado do handle deve conter pelo menos duas partes:
```text
slot_state:
loaded_asset_id
resident_state
request_state:
requested_asset_id
generation
queued/active/ready/canceled/superseded/error
backlog_position
progress
```
Mutacoes seguem regras mais estritas e devem falhar operacionalmente quando a
secao de request/backlog do handle nao representa a geracao atual.
## Progresso e Percentis
### Progresso percentual
Sugestao:
- usar progresso em base inteira `0..10000`;
- quando o tamanho total de bytes for conhecido, calcular progresso por fases
ponderadas;
- quando o decode nao for linear ou nao houver tamanho confiavel, expor
progresso por fase com marcos conservadores.
Modelo inicial de fases:
```text
queued -> 0
read -> 0..4000
decode -> 4000..9000
stage -> 9000..10000
ready -> 10000
```
Se uma fase nao consegue reportar progresso interno, ela deve manter o ultimo
marco e saltar para o proximo marco ao concluir. Isso evita progresso falso
sem perder visibilidade de fase.
### Percentis por asset
Sugestao:
- registrar duracoes por asset no fim de cada job, nao por tick/frame;
- manter agregadores pequenos por `asset_id` e por `bank_type`;
- calcular percentis a partir de janela/ring buffer pequeno ou histograma
barato por bucket de duracao;
- nunca atualizar percentis no loop quente de decode por item/pixel;
- o job ativo pode expor tempo decorrido e fase atual; percentis sao atualizados
apenas no fechamento do job.
## Perguntas em Aberto
- [x] A agenda esta madura para virar decisao normativa ou ainda falta algum
caso de asset/memcard/lane?
- [x] A estrutura minima de `slot_state` e `request_state` no handle esta
completa ou falta algum campo para commit/cancel/reorder seguro?
## Resolucao
A agenda esta madura para decisao normativa.
A decisao deve formalizar:
- a terceira async work lane serial;
- um job ativo por vez;
- backlog efetivamente limitado pela unicidade de `bank_type/slot`;
- handles como observadores estaveis de slots de bank;
- `slot_state` e `request_state` dentro do handle;
- cancelamento/superseding por novo request para o mesmo alvo;
- load pronto imediato quando o asset ja esta residente no alvo;
- decode/materializacao na lane async;
- commit/install na lane principal;
- memcard como consumidor prioritario da mesma lane;
- FS como consumidor permitido, deixando API publica de FS para `AGD-0006`;
- progresso por fases e percentis atualizados no fechamento do job.
## Dependencias ## Dependencias
- `../specs/09-events-and-concurrency.md` - `docs/specs/runtime/09-events-and-concurrency.md`
- `../specs/15-asset-management.md` - `docs/specs/runtime/15-asset-management.md`
- `../specs/16a-syscall-policies.md` - `docs/specs/runtime/16a-syscall-policies.md`
- `014-app-home-filesystem-surface-and-semantics.md` - `discussion/workflow/agendas/AGD-0006-app-home-filesystem-surface-and-semantics.md`
## Criterio de Saida Desta Agenda ## Criterio para Encerrar
Pode virar PR quando houver decisao escrita sobre: Pode virar PR quando houver decisao escrita sobre:
- numero e tipo de workers aceitos no baseline; - existencia e papel da terceira async work lane;
- fila/prioridade de `asset` e `fs`; - relacao dessa lane com a terceira core no hardware alvo e fallback no host;
- proibicao ou aceitacao limitada de `thread::spawn` por request; - desenho do backlog serial de asset loading;
- modelo de status/telemetria para backlog e saturacao. - regra de cancelamento/substituicao para requests no mesmo bank/slot;
- proibicao de `thread::spawn` por load no caminho normal;
- fases de asset load que rodam na lane vs lane principal;
- inclusao condicionada de FS/memcard nessa mesma lane;
- API de consulta e reordenacao do backlog de assets;
- comportamento de request imediato quando o asset ja esta residente no alvo;
- regra de bounded backlog por unicidade de `bank_type/slot`;
- politica de prioridade entre asset, FS e memcard;
- status e telemetria para backlog, progresso, percentis, cancelamento e falha.
## Discussao
- 2026-06-28: Agenda atualizada para remover render worker do escopo, assumir
uma terceira core/lane para trabalhos assincronos, exigir pool/fila limitada
para asset loading e reavaliar o papel de FS/memcard como consumidores da
mesma lane.
- 2026-06-28: Discussao consolidou que a async work lane deve ser serial, com
um job ativo, backlog semanticamente ilimitado, consulta/reordenacao publica
de backlog de assets, progresso percentual do asset ativo, decode na lane
async, install/commit na lane principal, e FS/memcard como consumidores a
alinhar com a agenda de app home filesystem.
- 2026-06-28: Acrescentada regra de asset pipe: requests para o mesmo bank/slot
devem cancelar pedidos anteriores pendentes para aquele alvo, tratando o
pedido mais novo como substituto operacional.
- 2026-06-28: Discussao refinou que o alvo canonico e `bank_type/slot`; um
asset ja residente deve retornar handle pronto sem entrar no backlog; o
backlog efetivo e limitado pela soma dos slots dos bancos; FS fica como
consumidor permitido da lane, enquanto API publica de FS permanece na agenda
6; progresso deve usar fases conservadoras e percentis devem ser atualizados
no fechamento do job, nao no hot path.
- 2026-06-28: Open questions restantes foram consolidadas: API de backlog
aceita com `promote/demote` como atalhos; superseding marca requests
substituidos por outro request para o mesmo `bank_type/slot`; cancelamento do
ativo e cooperativo quando barato e descarte por geracao quando nao for;
`read/decode/stage` cobre GLYPH/SOUND/SCENE; percentis ficam por `bank_type`
e por `asset_id`; API publica de FS permanece em `AGD-0006`.
- 2026-06-28: Adicionada a expectativa de que handles possam consultar o alvo
`bank_type/slot` a qualquer momento, inclusive quando nao houver asset valido
carregado; mutacoes devem continuar protegidas por request/geracao para evitar
commit/cancel acidental de pedidos substituidos.
- 2026-06-28: Refinado o modelo de handle: o handle pertence ao slot do bank e
contem uma secao de estado de request/backlog para aquele slot. Consulta e
residencia ficam estaveis por alvo; mutacoes atuam sobre a geracao atual do
request dentro do handle.

View File

@ -1,409 +0,0 @@
---
id: AGD-0010
ticket: perf-gfx-render-pipeline-and-dirty-regions
title: Agenda - [PERF] GFX Render Pipeline and Dirty Regions
status: open
created: 2026-03-27
resolved:
decision:
tags: []
---
# Agenda - [PERF] GFX Render Pipeline and Dirty Regions
## Problema
O renderer `gfx` recompõe a cena inteira a cada frame logico, mesmo quando a mudanca visual e pequena.
Hoje `render_all()` reconstrói buckets de sprites, escaneia os 512 sprites, redesenha layers e aplica dois passes fullscreen de fade sem politica de invalidacao.
Ao mesmo tempo, a arquitetura atual ja opera como um framebuffer destrutivo em memoria: draws escrevem diretamente no buffer de trabalho e operacoes posteriores sobrescrevem o que estiver por baixo. A discussao nao e migrar para um modelo tipo GPU, scene graph ou retained rendering.
Existe ainda um driver de produto importante: o objetivo continua sendo viabilizar hardware handheld proprio com limitacoes reais de orcamento e com economia de memoria o mais agressiva possivel. Isso significa que ganhos de CPU nao podem ser avaliados isoladamente; custo de RAM adicional, buffers extras e estruturas auxiliares entram diretamente no criterio de aceitacao.
## Dor
- custo visual basico cresce demais para hardware simples.
- pequenos updates pagam preco de full redraw.
- fade, HUD e world composition ficam sempre no caminho critico.
## Hotspots Atuais
- [gfx.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-drivers/src/gfx.rs#L563)
- [gfx.rs](/Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/crates/console/prometeu-drivers/src/gfx.rs#L671)
## Alvo da Discussao
Definir ate onde o v1 pode sofisticar o modelo de desenho destrutivo em memoria para reduzir custo e manter previsibilidade, sem migrar para um renderer de estilo GPU.
## O Que Precisa Ser Definido
1. Perfil real de custo.
Medir separadamente:
- rebuild de buckets;
- rasterizacao de layers;
- rasterizacao de sprites;
- fades fullscreen;
- conversao/copia no host;
- apresentacao no host.
2. Granularidade minima de invalidacao compativel com framebuffer destrutivo.
Escolher entre:
- dirty flag por frame inteiro;
- dirty flags por subsistema (`world`, `hud`, `fade`, `sprites`);
- dirty regions restritas e derivadas de primitivas/sprites;
- combinacao minima viavel.
3. Buckets e traversal de sprites.
Decidir se os buckets continuam sendo reconstruidos por frame ou se viram estrutura incremental.
4. Fades.
Definir se fade neutro precisa bypass total e se fade parcial pode operar por regiao/camada.
5. Cache de composicao.
Delimitar se layers estaticas/HUD podem manter cache intermediario.
6. Primitivas e contrato operacional.
Definir quais draws devem continuar sendo vistos como escrita destrutiva direta no framebuffer e onde valem aceleracoes internas sem mudar a semantica observavel.
7. Meta de custo.
Fechar qual teto de pixels/trabalho por frame e aceitavel no baseline.
8. Orcamento de memoria.
Definir quais otimizacoes aceitam memoria adicional e quais precisam caber no modelo mais economico possivel para handheld barato.
## Open Questions de Arquitetura
1. Quais partes do custo atual estao no `render_all()` e quais partes estao na conversao/apresentacao do host?
2. Qual o maior ganho de curto prazo dentro do modelo atual: buckets, fades, HUD, primitivas ou host copy/convert?
3. Dirty regions restritas quebram alguma expectativa de determinismo visivel ou sao apenas um detalhe interno de implementacao?
4. O HUD deve continuar no mesmo pipeline da world scene?
5. Quais primitivas merecem tratamento especial para reduzir overdraw sem mudar a semantica de sobrescrita?
6. Qual o teto aceitavel de memoria extra para caches, buffers separados ou estruturas auxiliares no handheld alvo?
## Problemas de Medio e Grande Porte Identificados
### 1. Composicao completa do framebuffer a cada frame logico
`render_all()` recompõe o `back` inteiro por software, mesmo quando a mudanca visual e pequena.
Impacto:
- custo cresce com a quantidade de pixels tocados, nao apenas com a quantidade de estado alterado;
- layers, sprites e fades disputam o mesmo budget de CPU;
- o modelo atual favorece previsibilidade sem ainda ter atalhos internos suficientes.
### 2. Rasterizacao pixel a pixel de tilemaps
Cada layer visivel percorre tiles e, para cada tile nao vazio, resolve indices e escreve pixel a pixel no framebuffer.
Impacto:
- o custo real esta mais na escrita de pixels do que na manutencao do `TileMap`;
- scroll e barato como estado, mas caro na hora de compor a imagem se tudo for repintado;
- layers estaticas continuam pagando custo de rasterizacao em toda recomposicao.
### 3. Rasterizacao pixel a pixel de sprites e escolha por bucket full scan
Os 512 sprites sao varridos para reconstruir buckets e os sprites ativos sao desenhados pixel a pixel.
Impacto:
- quando poucos sprites mudam, ainda existe custo fixo de rebuild dos buckets;
- overdraw entre sprites e layers pode explodir o numero de writes no `back`;
- a escolha de sprites ativos/prioridade ainda e simples demais para cenarios com alta ocupacao.
### 4. Fades fullscreen com custo proporcional ao frame inteiro
`scene fade` e `hud fade` percorrem o framebuffer inteiro quando ativos.
Impacto:
- custo grande e previsivel, mas potencialmente desproporcional;
- fade neutro ja pode ser bypass, mas fade parcial ainda custa uma passada completa;
- dois passes fullscreen no mesmo frame podem virar gargalo antes de layers ou sprites.
### 5. HUD no mesmo caminho critico da composicao principal
O HUD e redesenhado como tilemap depois da world scene e antes do `hud fade`.
Impacto:
- HUD estatico continua gerando trabalho por frame;
- mistura responsabilidades de world/UI no mesmo budget de composicao;
- dificulta isolar ganho de cache ou dirtying so para interface.
### 6. Conversao obrigatoria de RGB565 para RGBA8 no host
No `RedrawRequested`, o host percorre todo o `front_buffer` e converte para o frame do `pixels`.
Impacto:
- ha custo de leitura do framebuffer inteiro e escrita de um segundo buffer inteiro;
- esse custo existe mesmo quando o host esta apenas apresentando o mesmo quadro logico;
- pode competir com o custo do renderer sem estar visivel na discussao do `gfx`.
### 7. Pipeline serializado no host desktop
`tick`, `render_all()`, conversao de formato e apresentacao ocorrem no mesmo fluxo operacional.
Impacto:
- o frame time final acumula custo de runtime, composicao, copy/convert e present;
- falta desacoplamento para esconder latencia entre producao e apresentacao;
- dificulta atribuir gargalo sem instrumentacao por etapa.
## Estudo Inicial de Possiveis Optimizacoes
### A. Dirty flags por subsistema em vez de dirty rect generico
Ideia:
- flags independentes para `world`, `sprites`, `hud`, `scene_fade`, `hud_fade`, `host_present`.
Vantagens:
- preserva a semantica de framebuffer destrutivo;
- reduz recomposicao desnecessaria quando so um subsistema mudou;
- e muito mais simples de validar do que dirty regions arbitrarias.
Riscos:
- exige definir dependencias claras entre world, sprites, HUD e fades;
- pode induzir falsos positivos conservadores, o que e aceitavel no v1.
### B. Rebuild incremental ou condicional dos buckets de sprite
Ideia:
- so reconstruir buckets quando algum `GfxSetSprite` mudar atividade, prioridade, banco ou tile;
- opcionalmente manter contadores/sinais de mutacao da OAM.
Vantagens:
- elimina custo fixo por frame quando OAM permanece estavel;
- combina com a ideia de sprites como estado, nao como draw list efemera.
Riscos:
- precisa invalidacao correta para evitar bucket desatualizado;
- ganho pode ser pequeno se o custo dominante estiver no draw dos sprites, nao no rebuild.
### C. Separar world e HUD em buffers logicos distintos
Ideia:
- manter um buffer da world scene e um buffer do HUD, compondo no final apenas quando necessario.
Vantagens:
- HUD estatico deixa de participar da recomposicao da world;
- permite cache e fade especificos para cada dominio;
- se alinha bem com a separacao conceitual entre cena e interface.
Riscos:
- aumenta uso de memoria e pontos de sincronizacao;
- precisa manter semantica clara de sobrescrita entre mundo e HUD.
- pode ser inviavel no perfil de handheld barato se exigir buffers adicionais permanentes.
### D. Cache de layer estatica ou composicao parcial da world
Ideia:
- layers que nao mudam podem manter imagem intermediaria pronta;
- scroll, tile updates ou troca de bank invalidam o cache correspondente.
Vantagens:
- reduz rasterizacao repetida de cenario estavel;
- aproxima o ganho de hardware tile-based sem abandonar software raster.
Riscos:
- cache amplo demais vira complexidade estrutural;
- se scroll muda constantemente, o ganho cai bastante.
- pode consumir memoria demais para um hardware com orcamento agressivo.
### E. Otimizacao de fades
Ideia:
- bypass total para fade neutro;
- opcionalmente aplicar fade apenas sobre buffers relevantes;
- estudar LUTs ou blend mais barato por pixel.
Vantagens:
- ataca um custo fullscreen claramente delimitado;
- baixo risco conceitual.
Riscos:
- fade parcial por regiao pode complicar demais o contrato;
- LUT ajuda aritmetica, mas nao elimina custo de varrer memoria.
### F. Melhorias em primitivas e spans de memoria
Ideia:
- acelerar `fill_rect`, linhas horizontais/verticais, clears e possiveis blits contiguos;
- explorar caminhos de row spans contiguos em vez de loops mais genericos.
Vantagens:
- melhora direta do modelo de desenho destrutivo;
- baixo risco arquitetural;
- cria fundacao para outros atalhos internos.
Riscos:
- ganho localizado se o workload dominante vier de tile/sprite compositing;
- precisa medir por primitive class.
### G. Reduzir custo de copy/convert no host
Ideia:
- medir separadamente `draw_rgb565_to_rgba8` e `pixels.render()`;
- estudar formato mais proximo do host ou conversao mais barata;
- evitar redraw host quando nao houver novo front relevante.
Vantagens:
- ataca custo fora do `gfx` que ainda entra no frame time total;
- pode render ganho imediato no desktop host.
Riscos:
- parte do custo depende da stack `pixels/wgpu`, nao apenas do runtime;
- alguns ganhos podem ser especificos do host desktop e nao do contrato do console.
## Restricao de Plataforma
Qualquer recomendacao desta agenda deve ser filtrada por um criterio adicional:
- priorizar solucoes que melhorem custo sem multiplicar buffers;
- tratar memoria extra como recurso escasso de primeira classe;
- preferir flags, metadados pequenos e estruturas incrementais a caches grandes;
- evitar solucoes cuja performance dependa de assumir um host desktop mais forte do que o handheld alvo.
Direcao atual da discussao:
- nesta etapa, buffers extras devem ficar fora;
- o foco deve ser otimizar ao maximo o pipeline existente;
- cache intermediario ou novos buffers so entram em estudo depois que as otimizacoes de baixo custo de memoria forem medidas e esgotadas.
## Sugestao / Recomendacao Atual
Priorizar o estudo em camadas, nesta ordem:
1. instrumentar o pipeline por etapa;
2. validar dirty flags por subsistema como mecanismo minimo de invalidacao;
3. testar rebuild condicional de buckets e bypass/isolamento de fades;
4. otimizar primitivas e caminhos contiguos de escrita no framebuffer atual;
5. estudar separacao world/HUD e cache de layer apenas se os dados justificarem e o teto de memoria permitir;
5. tratar copy/convert do host como frente paralela de otimizacao, nao como substituto da analise do `gfx`.
## Achados Consolidados Ate Aqui
1. O contrato base continua sendo framebuffer destrutivo em memoria.
Draws escrevem diretamente no buffer de trabalho e operacoes posteriores sobrescrevem o que estiver por baixo. A agenda nao aponta para migracao a um renderer tipo GPU, retained mode ou compositor sofisticado.
2. O `present()` atual nao parece ser o problema principal.
No `gfx`, `present()` faz swap de buffers, nao copia completa de pixels.
3. O custo suspeito esta na recomposicao e na apresentacao, nao na manutencao do estado logico.
Escritas de `TileMap`, scroll e `GfxSetSprite` sao pequenas e pontuais; o custo potencialmente dominante esta em rasterizar pixels, aplicar fades fullscreen e converter o framebuffer para o host.
4. Os maiores suspeitos atuais de custo sao:
- rasterizacao de layers;
- rasterizacao de sprites;
- fades fullscreen;
- conversao `RGB565 -> RGBA8` no host;
- `pixels.render()` / apresentacao no host.
5. O pipeline atual deve ser otimizado antes de considerar buffers extras.
A direcao aceita da discussao e esgotar primeiro flags pequenas, bypasses, melhorias de primitive paths e ajustes incrementais no pipeline existente.
6. Restricao de plataforma pesa tanto quanto CPU.
Como o alvo e um handheld proprio com limitacoes de orcamento e memoria, solucoes que consumam RAM adicional significativa devem ficar fora desta fase.
7. A instrumentacao minima desejada para retomar o estudo ficou definida.
Quando houver workload representativo, queremos medir ao menos:
- `render_all_total_us`
- `bucket_rebuild_us`
- `layer_raster_us`
- `sprite_raster_us`
- `scene_fade_us`
- `hud_raster_us`
- `hud_fade_us`
- `host_convert_us`
- `host_present_us`
8. A retencao de metricas deve ser barata e orientada a analise posterior.
A preferencia atual e por acumuladores e ring buffer pequeno de snapshots, sem logging pesado por frame no hot path.
9. Ring buffer no `TileMap` nao e prioridade nesta fase.
O custo suspeito nao esta na manutencao da grade logica do mapa, e sim na composicao da janela visivel no framebuffer. Mudar a estrutura do mapa so faria sentido se o gargalo principal estivesse em streaming/atualizacao estrutural do cenario, o que nao e a hipotese atual.
10. A direcao de otimizacao mais promissora e padronizar copias massivas para o framebuffer atual.
Em vez de atacar apenas tilemaps com loops pixel a pixel, a discussao passa a favorecer um padrao geral de blit/copia massiva com:
- calculo previo de offsets e spans;
- fast paths para casos contiguos;
- trabalho por linha/chunk em vez de trabalho atomizado por pixel quando possivel;
- reaproveitamento dessa infraestrutura para tilemaps, sprites e outras operacoes de desenho destrutivo.
11. Migrar o `back` para `RGBA8888` nao e direcao aceita neste momento.
Isso aumentaria custo de memoria e largura de banda no alvo handheld, alem de deslocar a otimizacao para o host desktop. O contrato interno continua preferencialmente em `RGB565`, mesmo considerando fades e uma futura pipeline de lights.
## Nova Direcao Tecnica Em Estudo
Quando esta agenda for reaberta, a linha principal de investigacao deve considerar:
- manter o `TileMap` simples como estado logico;
- assumir que a janela visivel provavelmente continuara sendo recomposta na maior parte dos frames;
- concentrar a otimizacao em como essa recomposicao escreve no `back`;
- desenhar uma infraestrutura compartilhada de copias massivas/blits para o framebuffer atual;
- usar essa infraestrutura nao apenas para tilemaps, mas como base comum para raster de sprites e outros caminhos de desenho.
Pergunta orientadora da reabertura:
- como transformar o renderer de um pipeline de writes atomizados por pixel em um pipeline com fast paths de spans/chunks, sem perder a semantica de framebuffer destrutivo e sem pagar memoria extra relevante?
## Status de Standby
Esta agenda deve ficar em espera ate existir um game ou workload real que exercite de forma representativa:
- tilemaps com scroll e composicao de world;
- sprites em quantidade suficiente para testar buckets e overdraw;
- HUD ativo;
- fades;
- apresentacao completa no host.
Antes disso, qualquer conclusao sobre gargalo ou prioridade de otimizacao tende a ser prematura.
## Proximo Gatilho Para Reabrir
Reabrir esta agenda quando houver:
- um game jogavel ou cena de teste que percorra o pipeline completo;
- dados de workload mais proximos do uso real;
- necessidade concreta de justificar uma rodada de instrumentacao e profiling.
## Dependencias
- `../specs/04-gfx-peripheral.md`
- `../specs/11-portability-and-cross-platform-execution.md`
## Criterio de Saida Desta Agenda
Pode virar PR quando houver decisao escrita sobre:
- filosofia explicita de framebuffer destrutivo como contrato base;
- plano de instrumentacao para localizar o custo dominante do pipeline;
- nivel minimo de invalidacao no v1;
- politica de rebuild de buckets de sprites;
- bypass/cache de fade e HUD;
- politica para otimizar primitivas sem mudar a semantica observavel;
- meta de custo para o render pipeline.

View File

@ -0,0 +1,336 @@
---
id: DEC-0034
ticket: perf-async-background-work-lanes-for-assets-and-fs
title: Async Work Lane and Asset Backlog Contract
status: accepted
created: 2026-06-28
accepted: 2026-06-28
ref_agenda: AGD-0008
plans: [PLN-0123, PLN-0124, PLN-0125, PLN-0126, PLN-0127, PLN-0128]
tags: [perf, asset, fs, async, scheduler, runtime]
---
# DEC-0034 - Async Work Lane and Asset Backlog Contract
## Status
Accepted.
## Contexto
`asset.load()` currently creates an OS thread per non-resident asset request.
That behavior does not match the target runtime model: the machine should have
explicit execution lanes, bounded ownership, observable backpressure, and no
uncontrolled thread creation from guest-visible operations.
Render worker concurrency is already handled by separate render decisions. This
decision covers the third runtime lane for asynchronous IO/decode/persistence
work:
```text
Lane 1: VM, firmware, SystemOS, logical frame
Lane 2: render worker / asynchronous rasterization
Lane 3: async work lane for asset IO/decode and game persistence work
```
The concrete first consumer is asset loading. Game persistence through memcard
may also consume this lane. FS is allowed to consume this lane, but public FS API
shape belongs to the app-home filesystem agenda.
## Decisao
Prometeu MUST introduce a third async work lane as the runtime-owned execution
place for asset IO/decode work and compatible persistence IO work.
The async work lane MUST be serial: it has exactly one active job at a time.
The runtime MUST NOT create one OS thread per `asset.load` request in the normal
path.
Asset loading MUST use a backlog keyed by target `bank_type/slot`. A target can
have at most one current request. A newer request for the same `bank_type/slot`
MUST supersede the previous request for that target.
Asset handles MUST represent stable bank slots, not transient worker threads.
A handle MUST remain queryable even when the slot has no valid loaded asset and
even when there is no active request for that slot. The handle state MUST
separate resident slot state from request/backlog state.
Asset IO/read and decode/materialization MUST run on the async work lane.
Asset install/commit into resident banks MUST happen on the main runtime lane at
predictable ownership points.
Memcard work MAY consume the same async work lane and has higher priority than
ordinary asset loads when contention exists. FS MAY consume the same lane for
IO-style work, but this decision does not define the public FS API.
## Rationale
A serial lane maps cleanly to the intended third-core mental model and avoids a
desktop-biased generic thread pool. It gives the runtime one place to observe
IO/decode backlog, progress, priority, cancellation, and telemetry.
Keying requests by `bank_type/slot` makes the backlog naturally bounded by the
sum of bank slots. There is no need for a guest-visible `queue_full` state:
there cannot be unbounded distinct pending targets if each target has at most
one current request.
Keeping commit/install on the main lane preserves deterministic publication
semantics and avoids letting the async lane mutate resident graphics/audio/scene
state while the VM, render handoff, or frame boundary is observing it.
Separating `slot_state` and `request_state` inside the handle avoids ambiguity:
callers can inspect the slot at any time, while mutating operations still remain
protected by request generation.
## Invariantes / Contrato
### Async Work Lane
- The async work lane MUST be runtime-owned.
- The async work lane MUST be separate from the render worker.
- The async work lane MUST execute at most one active job at a time.
- The runtime MUST NOT use `thread::spawn` per asset request in the normal
asset loading path.
- The host may implement the lane as a fixed worker thread. Hardware targets may
map it to a dedicated core when available.
- If a physical third core is unavailable, the implementation MUST preserve the
same logical lane contract.
### Asset Backlog
- Asset requests MUST target a concrete `bank_type/slot`.
- The backlog is ordered and serial.
- Each `bank_type/slot` MUST have at most one current request.
- A newer request for the same `bank_type/slot` MUST supersede any earlier
pending request for that target.
- If the target already contains the requested `asset_id` as a valid resident
asset, `asset.load` MUST return a handle in ready state without adding a job
to the backlog.
- The effective backlog size is bounded by the sum of targetable bank slots,
even though the contract does not expose a fixed queue limit.
- Implementations MUST collect stale metadata for canceled or superseded
requests so the handle/request history does not grow without bound.
### Handles
An asset handle MUST represent a stable bank slot target. The handle state MUST
include at least:
```text
handle:
bank_type
slot
slot_state:
loaded_asset_id
resident_state
slot_generation
request_state:
requested_asset_id
request_generation
state
backlog_position
progress
```
The handle MUST be queryable when:
- the slot is empty;
- the slot contains a valid resident asset;
- a request is queued;
- a request is active;
- a request is ready for commit;
- a request has been canceled;
- a request has been superseded;
- a request ended in error.
Mutating operations such as commit, cancel, promote, demote, or move MUST act on
the current request generation. They MUST NOT accidentally mutate a newer
request through an older handle view.
### Superseding
`superseded` means a request was replaced by a newer request for the same
`bank_type/slot`.
- Superseding is an operational state, not a fault.
- If the old request is queued, it MUST be removed from the backlog.
- If the old request is active, the lane SHOULD cancel cooperatively when the
current phase supports cheap cancellation.
- If active work cannot be interrupted cheaply, it MAY finish, but its result
MUST be discarded when its generation no longer matches the target's current
request generation.
### Asset Backlog API Direction
The existing asset operations remain the base surface:
```text
asset.load(asset_id, slot) -> (status, handle)
asset.status(handle) -> status
asset.commit(handle) -> status
asset.cancel(handle) -> status
```
The asset backlog surface SHOULD add a small status-first API:
```text
asset.backlog_info()
-> (status, pending_count, active_handle, active_asset_id,
active_bank_type, active_slot, active_progress)
asset.backlog_position(handle)
-> (status, state, position, progress)
asset.backlog_move(handle, new_position)
-> status
asset.backlog_promote(handle)
-> status
asset.backlog_demote(handle)
-> status
asset.target_status(bank_type, slot)
-> (status, asset_id, handle, state, position, progress)
```
`asset.backlog_promote(handle)` is an official shortcut for moving a queued
request to position `1`, the first pending position after the active job.
`asset.backlog_demote(handle)` is an official shortcut for moving a queued
request to the end of the pending backlog.
The final ABI names and exact return shapes may be refined during planning, but
the implementation MUST preserve the capabilities above.
### Progress
Progress MUST be represented without floating point. The recommended scale is
`0..10000`.
The initial phase model is:
```text
queued -> 0
read -> 0..4000
decode -> 4000..9000
stage -> 9000..10000
ready -> 10000
```
If a phase cannot report internal progress, it MUST keep the previous progress
mark and advance at phase completion. Implementations MUST NOT invent false
precision for non-linear decode phases.
### Telemetry
Telemetry MUST be cheap and must not add hot-path per-item decode cost.
Minimum telemetry:
- current backlog depth;
- target/request position;
- active job progress;
- jobs submitted;
- jobs completed;
- jobs failed;
- jobs canceled;
- jobs superseded;
- job duration;
- percentiles by `bank_type`;
- lightweight percentiles or small-window samples by `asset_id`.
Percentiles MUST be updated when a job closes, not inside the inner decode loop.
### Priority
When async lane consumers contend, the initial priority order is:
1. memcard commit/write;
2. FS write/config work;
3. asset visual/audio/scene load;
4. non-critical list/read work.
This priority order is a lane arbitration rule. It does not define public FS
syscall semantics.
### FS Boundary
FS MAY consume the async work lane for IO-style work.
This decision MUST NOT define the public FS API, app-home FS semantics, or
whether FS is exposed as request/poll or sync-appearing operations. Those
questions belong to `AGD-0006`.
`AGD-0006` MUST be updated or interpreted with the existence of this async IO
lane in mind.
## Impactos
### Spec
- `docs/specs/runtime/15-asset-management.md` must absorb the async lane,
backlog, handle, superseding, progress, and telemetry contract.
- `docs/specs/runtime/16-host-abi-and-syscalls.md` must absorb any final public
asset backlog syscall names and return shapes.
- `docs/specs/runtime/16a-syscall-policies.md` may need status catalog updates
for superseded/canceled/backend unavailable states if not already covered by
asset domain status.
- `docs/specs/runtime/09-events-and-concurrency.md` should mention the async
work lane as an implementation-side lane that does not introduce guest
callbacks.
- `AGD-0006` / future FS spec work must account for FS as a possible consumer of
the async IO lane.
### Runtime
- Asset loading must stop spawning a thread per request.
- AssetManager needs a serial backlog keyed by `bank_type/slot`.
- Asset handles need stable slot identity plus separated `slot_state` and
`request_state`.
- Commit/install remains a main-lane operation.
- Superseding and generation checks become required correctness mechanisms.
### Host
- Desktop host may implement the async work lane with one fixed worker thread.
- Host presentation/render worker remains separate and unaffected.
### Firmware
- No direct firmware behavior is required for asset backlog mechanics.
- Firmware flows that rely on preload or memcard may later use lane telemetry or
priority policy if needed.
### Tooling
- Test tools may need support for inspecting backlog state and forcing request
ordering.
- Asset fixtures should cover queued, active, ready, canceled, superseded, and
already-resident paths.
## Referencias
- `AGD-0008` - Async Background Work Lanes for Assets and FS.
- `docs/specs/runtime/15-asset-management.md`
- `docs/specs/runtime/16-host-abi-and-syscalls.md`
- `docs/specs/runtime/16a-syscall-policies.md`
- `docs/specs/runtime/09-events-and-concurrency.md`
- `docs/specs/runtime/08-save-memory-and-memcard.md`
- `AGD-0006` - App Home Filesystem Surface and Semantics.
## Propagacao Necessaria
1. Update asset management specs with the lane/backlog/handle contract.
2. Update syscall specs if backlog APIs become public ABI.
3. Update FS agenda/spec work to acknowledge the async IO lane without deciding
FS public API in this decision.
4. Plan implementation of a serial async work lane and asset backlog.
5. Plan tests for superseding, stable slot handles, immediate-ready resident
assets, progress, telemetry, and main-lane commit.
## Revision Log
- 2026-06-28: Initial draft from `AGD-0008`.

View File

@ -0,0 +1,118 @@
---
id: PLN-0123
ticket: perf-async-background-work-lanes-for-assets-and-fs
title: Async Work Lane Specification Propagation
status: open
created: 2026-06-28
completed:
ref_decisions: [DEC-0034]
tags: [perf, asset, fs, async, scheduler, runtime, spec]
---
# PLN-0123 - Async Work Lane Specification Propagation
## Briefing
`DEC-0034` establishes a third serial async work lane, asset backlog semantics,
stable bank-slot handles, superseding, progress, telemetry, and FS/memcard lane
boundaries. The canonical specs must be updated before code work relies on the
new contract.
## Decisions de Origem
- `DEC-0034` - Async Work Lane and Asset Backlog Contract.
## Alvo
Publish the accepted async work lane and asset backlog contract in the canonical
runtime specs.
## Escopo
Included:
- Update asset management spec.
- Update syscall ABI spec if public backlog APIs are named there.
- Update syscall policy/status language for operational asset states.
- Update events/concurrency spec with the async work lane as an implementation
lane that does not introduce guest callbacks.
- Add a cross-reference from FS/app-home agenda or notes to the async IO lane.
Fora de Escopo:
- Implementing runtime code.
- Finalizing public FS API semantics.
- Changing render worker contracts.
## Plano de Execucao
### Step 1 - Update Asset Management
**What:** Add the third async lane, backlog, handle, superseding, progress, and
telemetry contract.
**How:** Extend `docs/specs/runtime/15-asset-management.md` around the existing
`asset.load/status/commit/cancel` section. Preserve the existing
`asset.load(asset_id, slot)` identity contract.
**Files:** `docs/specs/runtime/15-asset-management.md`.
### Step 2 - Update Host ABI
**What:** Add the backlog API direction if the ABI spec is the canonical place
for syscall shapes.
**How:** Extend the asset surface section with the planned backlog operations,
marking exact numeric ids as implementation-plan work if ids are not assigned
yet.
**Files:** `docs/specs/runtime/16-host-abi-and-syscalls.md`.
### Step 3 - Update Syscall Policies
**What:** Clarify that `superseded`, `canceled`, `backend_unavailable`, and
decode failures are operational statuses, not faults.
**How:** Add asset-domain examples under status-first policy.
**Files:** `docs/specs/runtime/16a-syscall-policies.md`.
### Step 4 - Update Events and Concurrency
**What:** Document that the async work lane is an implementation lane.
**How:** Add a section next to render worker concurrency stating that asset/IO
lane work does not introduce guest callbacks or hidden guest execution.
**Files:** `docs/specs/runtime/09-events-and-concurrency.md`.
### Step 5 - Cross-Reference FS Work
**What:** Record that FS may consume the async IO lane, while public FS API
semantics remain owned by the app-home filesystem discussion.
**How:** Add a note in `AGD-0006` or its future decision input, without deciding
FS request/poll shape in this plan.
**Files:** `discussion/workflow/agendas/AGD-0006-app-home-filesystem-surface-and-semantics.md`.
## Criterios de Aceite
- [ ] Specs state that the async work lane is serial and separate from render.
- [ ] Asset spec states backlog keying by `bank_type/slot`.
- [ ] Asset spec states stable slot handles with `slot_state` and `request_state`.
- [ ] Asset spec states superseding behavior.
- [ ] Asset spec states main-lane commit/install.
- [ ] FS is cross-referenced as a permitted lane consumer without public API decisions.
## Tests / Validacao
- Run `discussion validate`.
- Review spec links for broken relative paths.
- No cargo tests are required for this documentation-only plan.
## Riscos
- Public ABI names may be refined later; the spec must avoid assigning ids too
early if implementation work has not reserved them.
- FS scope can leak into this work; keep FS public API decisions in `AGD-0006`.

View File

@ -0,0 +1,119 @@
---
id: PLN-0124
ticket: perf-async-background-work-lanes-for-assets-and-fs
title: Async Work Lane Runtime Infrastructure
status: open
created: 2026-06-28
completed:
ref_decisions: [DEC-0034]
tags: [perf, asset, fs, async, scheduler, runtime]
---
# PLN-0124 - Async Work Lane Runtime Infrastructure
## Briefing
`DEC-0034` requires one runtime-owned async work lane, separate from VM/main
execution and separate from render worker execution. This plan introduces the
lane infrastructure before migrating asset loading.
## Decisions de Origem
- `DEC-0034` - Async Work Lane and Asset Backlog Contract.
## Alvo
Create a serial async work lane primitive that can execute one active job at a
time, expose observable state, and support deterministic shutdown.
## Escopo
Included:
- Runtime-owned async work lane type.
- Single active job execution.
- Ordered pending job storage.
- Cooperative cancellation hook.
- Shutdown and join behavior.
- Basic lane telemetry.
Fora de Escopo:
- Public asset backlog syscalls.
- Asset handle model migration.
- FS public API.
- Render worker changes.
## Plano de Execucao
### Step 1 - Locate Runtime Service Boundary
**What:** Choose the crate/module boundary for the async lane.
**How:** Place generic lane infrastructure near VM runtime services, not inside
desktop host code and not inside render worker modules.
**Files:** likely `crates/console/prometeu-system/src/services/` or
`crates/console/prometeu-system/src/services/vm_runtime/`.
### Step 2 - Define Job and Lane Types
**What:** Add a lane type with pending queue, active job metadata, generation,
state, progress, and cancellation flag.
**How:** Use typed job enums or traits that do not require guest callbacks.
Start with internal asset job support but keep the lane generic enough for
memcard/FS later.
**Files:** new module under `prometeu-system` service layer.
### Step 3 - Implement Serial Worker
**What:** Implement one worker loop that takes one pending job and runs it to
completion or cooperative cancellation.
**How:** Use a fixed worker thread on host builds. Preserve a logical-lane API
so future hardware can map it to a dedicated core.
**Files:** async lane module; lifecycle wiring in VM runtime service setup.
### Step 4 - Implement Shutdown
**What:** Add explicit stop/shutdown behavior.
**How:** Wake the lane, stop accepting new jobs, report pending jobs as canceled
or shutdown-discarded, and join the worker with bounded behavior.
**Files:** async lane module and VM runtime lifecycle modules.
### Step 5 - Expose Internal Telemetry
**What:** Add counters for pending depth, active job, submitted, completed,
failed, canceled, and superseded.
**How:** Use cheap atomics or lane-owned state snapshots. Do not update
expensive percentile data inside inner decode loops.
**Files:** async lane module; telemetry structs where runtime telemetry lives.
## Criterios de Aceite
- [ ] Lane has one active job maximum.
- [ ] Lane is separate from render worker code.
- [ ] Lane can be started and stopped deterministically.
- [ ] Pending jobs are observable.
- [ ] Cooperative cancellation can be signaled.
- [ ] No guest callback path is introduced.
## Tests / Validacao
- Unit tests for enqueue, single active execution, ordering, cancellation signal,
and shutdown.
- Concurrency tests must synchronize on real state transitions, not sleeps.
- Run targeted `cargo test` for the owning crate.
## Riscos
- Accidentally creating a generic pool instead of a serial lane.
- Coupling the lane to desktop host thread details.
- Reusing render worker handoff semantics where ordered backlog is required.

View File

@ -0,0 +1,127 @@
---
id: PLN-0125
ticket: perf-async-background-work-lanes-for-assets-and-fs
title: Asset Backlog and Stable Slot Handles
status: open
created: 2026-06-28
completed:
ref_decisions: [DEC-0034]
tags: [perf, asset, async, runtime]
---
# PLN-0125 - Asset Backlog and Stable Slot Handles
## Briefing
`DEC-0034` changes asset loading from request-owned thread spawning to a serial
backlog keyed by `bank_type/slot`. Handles become stable bank-slot observers
with separate resident slot state and request state.
## Decisions de Origem
- `DEC-0034` - Async Work Lane and Asset Backlog Contract.
## Alvo
Refactor `AssetManager` so asset requests are tracked by target bank slot,
deduplicated/superseded by target, and observable through stable handles.
## Escopo
Included:
- Stable handle structure with `bank_type`, `slot`, `slot_state`, and
`request_state`.
- Backlog keyed by `bank_type/slot`.
- Already-resident fast path.
- Superseding for queued and active requests.
- Main-lane commit/install boundary.
Fora de Escopo:
- Public backlog syscall wiring.
- Percentile telemetry.
- FS public API.
## Plano de Execucao
### Step 1 - Model Slot Handles
**What:** Replace transient load-operation assumptions with stable bank-slot
handle state.
**How:** Introduce structures equivalent to:
```text
handle: bank_type, slot
slot_state: loaded_asset_id, resident_state, slot_generation
request_state: requested_asset_id, request_generation, state, position, progress
```
**Files:** `crates/console/prometeu-drivers/src/asset.rs` and HAL asset types if
public type definitions belong there.
### Step 2 - Key Backlog by Target
**What:** Ensure at most one current request per `bank_type/slot`.
**How:** Add a target index from `(bank_type, slot)` to current request
generation/handle state. Enqueue only when the target does not already have the
same resident asset and no newer request supersedes it.
**Files:** `crates/console/prometeu-drivers/src/asset.rs`.
### Step 3 - Implement Already-Resident Fast Path
**What:** Return a ready handle immediately when the requested asset already
resides in the target slot.
**How:** Compare requested `asset_id` with the target `slot_state.loaded_asset_id`
and resident validity before creating a queued job.
**Files:** `crates/console/prometeu-drivers/src/asset.rs`.
### Step 4 - Implement Superseding
**What:** New request for the same target supersedes previous queued or active
request.
**How:** Remove queued older request immediately. For active work, update target
generation so the old result is discarded if it cannot stop cooperatively.
**Files:** `crates/console/prometeu-drivers/src/asset.rs`; async lane module
from `PLN-0124`.
### Step 5 - Preserve Main-Lane Commit
**What:** Keep install/commit into resident banks on the main runtime lane.
**How:** Async lane produces staged materialized result. `asset.commit(handle)`
installs only when the request generation is current and ready.
**Files:** `crates/console/prometeu-drivers/src/asset.rs`; VM runtime dispatch if
commit behavior changes.
## Criterios de Aceite
- [ ] No asset load spawns an OS thread per request.
- [ ] At most one request exists per `bank_type/slot`.
- [ ] Same-target newer request supersedes older queued request.
- [ ] Same-target newer request prevents active stale result from installing.
- [ ] Already-resident target returns ready handle without backlog entry.
- [ ] Handles remain queryable for empty, queued, active, ready, committed,
canceled, superseded, and error states.
## Tests / Validacao
- Unit tests for handle state transitions.
- Unit tests for target-keyed superseding.
- Unit tests for already-resident fast path.
- Integration test for stale active result discard.
- Existing asset load/commit tests must continue passing.
## Riscos
- Confusing handle identity with request generation.
- Installing a stale async result after superseding.
- Losing compatibility with existing `asset.status/commit/cancel` behavior.

View File

@ -0,0 +1,124 @@
---
id: PLN-0126
ticket: perf-async-background-work-lanes-for-assets-and-fs
title: Asset Backlog Public API and Status Surface
status: open
created: 2026-06-28
completed:
ref_decisions: [DEC-0034]
tags: [perf, asset, async, syscall, runtime]
---
# PLN-0126 - Asset Backlog Public API and Status Surface
## Briefing
`DEC-0034` keeps `asset.load/status/commit/cancel` and adds a small status-first
backlog inspection and ordering surface. This plan wires that surface through
HAL syscall metadata and runtime dispatch.
## Decisions de Origem
- `DEC-0034` - Async Work Lane and Asset Backlog Contract.
## Alvo
Expose asset backlog inspection and reordering while preserving status-first
semantics and existing asset load identity.
## Escopo
Included:
- Syscall registry additions for backlog APIs.
- Runtime dispatch handlers.
- Status enum additions or normalization.
- ABI/spec alignment with `PLN-0123`.
Fora de Escopo:
- Async lane internals.
- Asset decode refactor.
- FS API.
## Plano de Execucao
### Step 1 - Finalize Syscall Shapes
**What:** Choose final names, arity, and return slots for backlog operations.
**How:** Start from `DEC-0034`:
```text
asset.backlog_info()
asset.backlog_position(handle)
asset.backlog_move(handle, new_position)
asset.backlog_promote(handle)
asset.backlog_demote(handle)
asset.target_status(bank_type, slot)
```
**Files:** `crates/console/prometeu-hal/src/syscalls/domains/asset.rs`,
`docs/specs/runtime/16-host-abi-and-syscalls.md`.
### Step 2 - Add Status Values
**What:** Represent `empty`, `invalid`, `queued`, `active`, `ready`,
`committed`, `canceled`, `superseded`, `error`, and `backend_unavailable`.
**How:** Extend or map existing `LoadStatus` and `AssetOpStatus` without
turning operational states into traps.
**Files:** HAL asset status types and `crates/console/prometeu-drivers/src/asset.rs`.
### Step 3 - Add Dispatch
**What:** Wire new syscalls through VM runtime dispatch.
**How:** Add match arms near existing `AssetLoad`, `AssetStatus`,
`AssetCommit`, and `AssetCancel` arms. Return status-first values.
**Files:** `crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs`.
### Step 4 - Implement API Methods
**What:** Add AssetManager methods backing each public operation.
**How:** Methods must read stable handle/target state and reorder only queued
requests. `promote` and `demote` are shortcuts over move semantics.
**Files:** `crates/console/prometeu-drivers/src/asset.rs`.
### Step 5 - Validate ABI Metadata
**What:** Ensure registry ids, args, returns, capability, cost, and
non-deterministic flags are correct.
**How:** Update syscall metadata tests and any declared PBS/runtime syscall
tables.
**Files:** `crates/console/prometeu-hal/src/syscalls/tests.rs` and related
registry files.
## Criterios de Aceite
- [ ] Public backlog operations are registered with stable metadata.
- [ ] All backlog operations are status-first.
- [ ] `promote` is equivalent to moving to first pending position.
- [ ] `demote` moves to the end of pending backlog.
- [ ] Reordering a non-queued request returns operational status.
- [ ] Structural ABI misuse remains Trap.
## Tests / Validacao
- HAL syscall metadata tests.
- VM runtime dispatch tests for each new syscall.
- AssetManager unit tests for move/promote/demote.
- Tests for `target_status` on empty, queued, active, ready, and superseded
targets.
## Riscos
- Adding too many public ABI details before spec propagation is accepted.
- Returning payload values that are ambiguous when status is not success.
- Treating known but non-mutable states as traps instead of statuses.

View File

@ -0,0 +1,120 @@
---
id: PLN-0127
ticket: perf-async-background-work-lanes-for-assets-and-fs
title: Async Lane Memcard and FS Integration Boundaries
status: open
created: 2026-06-28
completed:
ref_decisions: [DEC-0034]
tags: [perf, fs, memcard, async, runtime]
---
# PLN-0127 - Async Lane Memcard and FS Integration Boundaries
## Briefing
`DEC-0034` allows memcard and FS work to consume the async IO lane but keeps
public FS API semantics out of scope. This plan establishes integration
boundaries without deciding app-home FS API shape.
## Decisions de Origem
- `DEC-0034` - Async Work Lane and Asset Backlog Contract.
## Alvo
Define and implement safe internal boundaries for memcard and FS use of the
async work lane, preserving lane priority and keeping FS public API decisions in
`AGD-0006`.
## Escopo
Included:
- Memcard as a priority lane consumer candidate.
- FS as a permitted internal lane consumer.
- Priority ordering in the lane.
- Updates to `AGD-0006` to acknowledge the lane.
Fora de Escopo:
- Public FS request/poll design.
- App-home path/status contract.
- Full memcard async conversion if it requires public ABI changes.
## Plano de Execucao
### Step 1 - Update FS Agenda
**What:** Record that an async IO lane exists and may be used by FS.
**How:** Add a concise section to `AGD-0006` stating that FS public API remains
owned by that agenda, but implementation may use the async lane from `DEC-0034`.
**Files:** `discussion/workflow/agendas/AGD-0006-app-home-filesystem-surface-and-semantics.md`.
### Step 2 - Identify Memcard Operations
**What:** List memcard operations that can benefit from async IO.
**How:** Inspect `crates/console/prometeu-system/src/services/memcard.rs` and
VM runtime dispatch for `mem.*` operations. Classify read, write, commit, and
clear behavior.
**Files:** memcard service and VM runtime dispatch modules.
### Step 3 - Define Lane Priority
**What:** Encode the initial priority order from `DEC-0034`.
**How:** Add priority classes:
```text
memcard commit/write
FS write/config
asset load
non-critical list/read
```
**Files:** async lane module from `PLN-0124`.
### Step 4 - Add Internal Consumer Hooks
**What:** Provide internal APIs for memcard/FS to submit lane jobs later.
**How:** Add types/interfaces without changing public FS syscall behavior.
No public FS request/poll operations are introduced here.
**Files:** async lane module; FS/memcard service modules as needed.
### Step 5 - Preserve Current Public Behavior
**What:** Ensure existing memcard and FS syscalls keep their current observable
surface unless a later plan explicitly changes it.
**How:** Do not alter return shapes in this plan. Add tests proving existing
status-first behavior remains intact.
**Files:** VM runtime FS/memcard tests.
## Criterios de Aceite
- [ ] `AGD-0006` acknowledges the async IO lane and keeps FS public API in its
own scope.
- [ ] Async lane supports priority classes required by `DEC-0034`.
- [ ] Memcard and FS can be represented as internal lane consumers.
- [ ] Existing public memcard/FS syscall shapes are unchanged.
- [ ] No FS public request/poll API is introduced by this plan.
## Tests / Validacao
- Existing FS/memcard tests continue passing.
- Unit tests for lane priority ordering.
- Tests proving asset jobs do not overlap with higher-priority memcard jobs
once both are submitted through the lane.
## Riscos
- Accidentally deciding FS public API in the wrong discussion.
- Making memcard async in a way that changes status-first behavior.
- Starving asset loads if priority rules do not include fairness.

View File

@ -0,0 +1,126 @@
---
id: PLN-0128
ticket: perf-async-background-work-lanes-for-assets-and-fs
title: Async Asset Pipeline Contract Tests and Telemetry
status: open
created: 2026-06-28
completed:
ref_decisions: [DEC-0034]
tags: [perf, asset, async, telemetry, tests, runtime]
---
# PLN-0128 - Async Asset Pipeline Contract Tests and Telemetry
## Briefing
`DEC-0034` requires observable progress, backlog state, superseding, and cheap
telemetry. This plan adds the validation and telemetry evidence needed to prove
the new async asset pipeline is correct.
## Decisions de Origem
- `DEC-0034` - Async Work Lane and Asset Backlog Contract.
## Alvo
Provide contract tests and telemetry for the async asset lane so future
implementations can be changed without regressing lane semantics.
## Escopo
Included:
- Contract tests for lane and asset backlog behavior.
- Progress reporting tests.
- Superseding and stale result discard tests.
- Telemetry counters and percentile updates.
- No-sleep deterministic test helpers where needed.
Fora de Escopo:
- Implementing public API wiring if `PLN-0126` has not run.
- Full benchmarking infrastructure.
- Host UI display of telemetry.
## Plano de Execucao
### Step 1 - Build Deterministic Test Hooks
**What:** Add test hooks for blocking a lane job at read/decode/stage.
**How:** Use channels/barriers or explicit fake job steps. Do not rely on
`thread::sleep` for correctness.
**Files:** async lane tests; asset test support modules.
### Step 2 - Test Backlog Ordering
**What:** Prove queued jobs run serially and in backlog order.
**How:** Submit multiple target slots and assert active job, pending positions,
promote, demote, and move behavior.
**Files:** asset manager tests and lane tests.
### Step 3 - Test Superseding
**What:** Prove newer request for the same `bank_type/slot` supersedes older
requests.
**How:** Cover queued superseding and active superseding. For active work, prove
stale completion cannot install.
**Files:** asset manager tests.
### Step 4 - Test Stable Handle Observability
**What:** Prove handles are queryable for empty, queued, active, ready,
committed, canceled, superseded, and error states.
**How:** Use `target_status` and handle status APIs to inspect both `slot_state`
and `request_state`.
**Files:** asset manager tests; VM dispatch tests if public API is available.
### Step 5 - Test Progress
**What:** Prove progress uses `0..10000` and phase boundaries.
**How:** Fake read/decode/stage phases with known completion points. Validate
non-linear phases do not report false internal progress.
**Files:** asset manager tests; telemetry tests.
### Step 6 - Add Telemetry
**What:** Add counters and percentile aggregation.
**How:** Update submitted/completed/failed/canceled/superseded counters when job
state closes. Update percentiles by `bank_type` and lightweight `asset_id`
window only at job completion.
**Files:** asset manager telemetry, async lane telemetry, existing runtime
telemetry types if shared.
## Criterios de Aceite
- [ ] Tests prove one active job at a time.
- [ ] Tests prove backlog order and reordering.
- [ ] Tests prove queued and active superseding.
- [ ] Tests prove stale active results are discarded.
- [ ] Tests prove handles remain queryable across all required states.
- [ ] Tests prove progress phase behavior.
- [ ] Telemetry is updated at job closure, not inside inner decode loops.
## Tests / Validacao
- Run targeted cargo tests for asset manager, async lane, and VM runtime
dispatch.
- Run existing asset-bank tests to prove current behavior remains compatible.
- Run `discussion validate` after plan state changes.
## Riscos
- Tests that depend on sleeps will be flaky.
- Telemetry may become too expensive if updated inside decode loops.
- Public API tests must wait for `PLN-0126` if syscall wiring is not present.