Přeskočit na obsah

Ukládání & paměť

Tyto tři helpery řeší části PicoPad hry, které bojují s omezenou RAM a flashem zařízení: udržení malého stavu mezi restarty (picogame_save), vyhnutí se MemoryError z fragmentované haldy (picogame_arena) a zobrazování sprite sheetů příliš velkých na to, aby se načetly celé (picogame_stream). Pro samotné signatury viz /cs/reference/; pro rozpočet RAM, který motivuje ty poslední dva, viz /cs/memory/.

Malé strukturované úložiště klíč-hodnota postavené na microcontroller.nvm - vyhrazené 4 KB oblasti ve flashi RP2040, do které můžeš přímo zapisovat z code.py. Sáhni po něm, když chceš, aby high score, odemčená úroveň nebo nastavení přežily vypnutí. Na rozdíl od souborového systému CIRCUITPY (jen pro čtení z kódu, pokud ho nepřipojíš znovu v boot.py), NVM nevyžaduje žádný boot.py, žádný reflash a přežije i smazání souborového systému.

NVM je jedna sdílená oblast pro všechny programy na zařízení, takže každá hra předává vlastní key. Klíč se zahashuje do hlavičky a při načítání se zkontroluje - pokud jiná hra nebo stará data zapsala ten slot, load() vrátí tvoje výchozí hodnoty místo špatně přečtených cizích bajtů.

Data popisuješ pomocí schématu: seřazeného slovníku name -> (struct format char, default). Běžné znaky: "B" 0-255, "H" 0-65535, "I" 0 až 2^32-1; malá b/h/i jsou se znaménkem.

  • Save(key, schema, *, offset=0) - vytvoří úložiště. key je název tvé hry (str nebo bytes). offset je jen klíčové slovo; zvedni ho pouze pokud dvě souběžně běžící hry musí používat různé oblasti NVM. Vyhodí RuntimeError, pokud NVM není dostupná, nebo ValueError, pokud se schéma do NVM nevejde.
  • load() - vrátí slovník uložených hodnot, nebo čerstvou kopii výchozích hodnot, pokud je slot prázdný, poškozený nebo zapsaný jinou hrou (neshoda klíče). Na špatná data nikdy nevyhodí výjimku.
  • save(values) - uloží slovník. Chybějící klíče se doplní výchozí hodnotou ze schématu. Zapíše kontrolní součet, aby ho pozdější load() mohl detekovat jako poškozený.
  • reset() - zapíše výchozí hodnoty zpět pod klíčem této hry.
  • defaults() - čerstvý slovník pouze s výchozími hodnotami, bez čtení NVM.
import picogame_save
# persist the best lap time (seconds) across reboots
store = picogame_save.Save("ghostrace", {"best_t": ("H", 0)})
best_t = store.load()["best_t"] # 0 = no record yet
# ...later, on a new best run:
if best_t == 0 or secs < best_t:
best_t = secs
store.save({"best_t": best_t}) # survives power-off

Pozor: každé save() smaže a přepíše sektor flashe, takže flash se opotřebuje, pokud ho voláš každý snímek - ukládej jen při smysluplných událostech (game over, nové high score, změna nastavení). Schéma udržuj malé; celý blob (hlavička + pole + kontrolní součet) se musí vejít do NVM.

Předalokovaná bufferová aréna, která rozdává plátky jednoho velkého bufferu, takže velké povrchy za běhu nic nealokují ani neuvolňují. Sáhni po ní, když dlouhobě běžící hra opakovaně vytváří velké Canvas povrchy a nakonec narazí na MemoryError, přestože gc.mem_free() vypadá v pořádku. MicroPython GC haldu nikdy nekompaktuje, takže točení velkých bufferů ji fragmentuje: spousta celkové volné RAM, ale žádný dost velký souvislý blok. Aréna si jeden blok vezme najednou, brzy, dokud je halda čerstvá.

  • Arena(pixels) - alokuje arénu. Velikost je v pixelech; rezervuje pixels * 2 bajtů (RGB565). Udělej to brzy, než se halda fragmentuje.
  • canvas(w, h, transparent=None) - pg.Canvas podložený dalším plátkem (bez alokace haldy na canvas); automaticky 16-bitově zarovnaný. Vrátí Canvas.
  • alloc(nbytes, align=1) - generický memoryview plátek o nbytes: znovupoužij ho jako buffer pro čtení souboru/sítě, pomocný prostor pro parsování, audio blok atd. align zaokrouhlí začátek plátku nahoru (použij align=2 pro 16-bitová data, align=4 pro přístup po slovech). Vyhodí MemoryError, pokud je aréna plná. Platný do dalšího reset().
  • reset() - uvolní všechny dosud vydané plátky. Volej na začátku každé scény, která arénu znovu používá. Jakýkoli Canvas před resetem se nesmí dále kreslit.
  • free() - bajty stále dostupné v aréně.
import picogame_arena
# one arena for the big canvases, grabbed once while the heap is contiguous;
# scenes that never run at the same time share the bytes (reset each).
ARENA = picogame_arena.Arena(320 * 80) # 320x80 px = 51 200 bytes
def big_canvas(w, h, transparent=None, first=False):
if first:
ARENA.reset() # reuse the arena for this scene
return ARENA.canvas(w, h, transparent=transparent)

Pozor: tohle vyžaduje firmware Canvas s argumentem buffer= - na simulátoru je tento argument ignorován a simulátor alokuje vlastní buffer, takže přínos proti fragmentaci se projeví jen na skutečném hardwaru. Po reset() jsou všechny povrchy z předchozí dávky mrtvé; kreslení do jednoho poškodí nový obsah.

Přehrává velký sprite sheet přímo ze souboru ve flashi a drží v RAM jen jeden snímek. Sáhni po něm, když je sheet příliš velký na to, aby se importoval celý - .mpy import zkopíruje celý sheet na haldu, ale StreamSheet soubor otevře jednou a při každém požadavku na snímek provede readinto() do bufferu pro jeden snímek (bez alokace na snímek). Pro sheet 64x100, 11 snímků to znamená ~6,4 KB rezidentní paměti místo ~70 KB. Sheet musí být frame-major (bajty každého snímku w*h jsou za sebou) - zabal ho pomocí tools/pack_sheet.py.

  • StreamSheet(pg, path, w, h, frames, palette, transparent=None) - otevře path, alokuje buffer pro jeden snímek, sestaví pg.Bitmap (PAL8) nad ním a načte snímek 0. palette je tabulka barev pro PAL8 data.
  • .bitmap - jediný pg.Bitmap, jehož pixely se přepisují na místě. Z tohoto sestav svůj pg.Sprite.
  • use(i) - načte snímek i (zabalený modulo frames) do sdíleného bufferu a vrátí bitmap. Cachovaný: znovu čte z flashe jen když se i skutečně změní.
  • close() - zavře podkladový soubor.
import picogame_stream
sheet = picogame_stream.StreamSheet(pg, "jill.bin", 64, 100, 11, PAL, transparent=0)
player = pg.Sprite(sheet.bitmap, x, y)
# ...each frame the animation advances:
sheet.use(frame_index) # stream that frame into the shared buffer
player.touch() # tell the scene to repaint it (pixels changed in place)

Pozor: vždy volej sprite.touch() po use(). use() přepíše jeden bitmap buffer na místě, ale dirty-rect engine scény překreslí sprite jen tehdy, když se změní sledovaná vlastnost (pozice, index snímku, měřítko, úhel, objekt bitmapy). Změna pixelů na místě je pro něj neviditelná, takže bez touch() změna snímku bez pohybu (vrchol výskoku, chůze do zdi) zanechá zastaralý nebo roztrhaný sprite. Sprite.touch() vyžaduje firmware z picogame engine 2026-06 nebo novějšího; na simulátoru je to no-op (simulátor překresluje celý obraz). Viz /cs/scene-format/, jak dirty-rect překreslování funguje.