2016-01-14 39 views
11

Piszę swój pierwszy prawdziwy projekt Haskell i mam problem z organizacją stanu w programie. Jest to emulator Gameboy Color, więc istnieje wiele małych flag, a cały stan wyglądaJak organizować duże ilości państw w projektach Haskell

data Memory s = Memory { memory :: STUArray s Word16 Word8 
         , registers :: STUArray s Word8 Word8 
         , sp :: STRef s Word16 
         , pc :: STRef s Word16 
         , cycles :: STRef s Word16 
         , ime :: STRef s Bool --Interrupt Master Enable Flag 
         , halt :: STRef s Bool --Are we halted or not 
         , mode :: STRef s GPUMode -- GPU mode 
         , line :: STRef s Word8 -- GPU line 
         , transferred :: STRef s Bool 
         , gpuCycles :: STRef s Word16 
         , window :: Window 
         , renderer :: Renderer 
         } 

I robię wszystko odczytu/zapisu stanu jak:

data Address = OneRegister Register 
      | TwoRegister {registerA :: Register, registerB :: Register} 
      | MemAddr Word16 
      | SP 
      | PC 
      | CYCLES 
      | IME 
      | HALT_STATE 
      | GPU_MODE 
      | GPU_LINE 
      | GPU_TRANSFERRED_LINE 
      | GPU_CYCLES 

    data MemVal = MemVal8 Word8 
      | MemVal16 Word16 
      | Flag Bool 
      | Mode GPUMode 

    read :: Memory s -> Address -> ST s MemVal 
    write :: Memory s -> Address -> MemVal -> ST s() 

widać : https://github.com/nikhilunni/HaskellBoy/blob/master/src/Memory.hs

Czy istnieje jakiś bardziej przejrzysty sposób na uporządkowanie wszystkiego? Chciałbym podzielić stan między różne komponenty (procesor, GPU, przełączanie banku kaset itp.), Jeśli to możliwe. Czy to jest idiomatyczne mieć duży monolityczny typ stanu w Haskell?

To bardzo duży problem z dodaniem nowego stanu do programu. Pakiet Control.Lens wydaje się być na dobrej drodze, ale nie jestem pewien, czy mogę go łatwo połączyć z ST.

Dzięki!

+8

Początkowo myślałem "ha, jak duże byłoby to państwo?". Ale * "Piszę swój pierwszy prawdziwy projekt Haskell *, [...] *** emulator Gameboy Color" ***. Ambitne pierwsze kroki. – Zeta

Odpowiedz

5

Soczewki są na pewno bardzo pomocne dla tego rodzaju rzeczy, ale wolisz korzystać z nich z dużym, czystymzagnieżdżonego obiektu państwowej w State monady, zamiast ST. I myślę, że będzie prawdopodobnie ok dla wszystkich tych zmiennych, choć będzie to prawdopodobnie nie do zaakceptowania dla tablic (które musiałyby być głęboko kopiowane z każdą modyfikacją

Więc mogę myśleć o dwóch opcji.

  • Przejście z tablic do struktury danych z wydajnymi czystych aktualizacjach funkcjonalnych, takich jak Sequence. Rów te STRefs całkowicie na korzyść aktualizacjach soczewek opartego na State.
    To będzie nigdzie tak skuteczny jak destrukcyjnych aktualizacjach tablicy w ST, ale do symulacji Game Boy na szybkim współczesnym komputerze może po prostu zadziałać
  • Podziel typ pamięci, aby zachować tablice w ST, ale zgrupuj wszystkie inne stany w pojedynczej STRef z czystą strukturą danych. Możesz wtedy użyć soczewek.

    data Memory s = Memory { memory :: STUArray s Word16 Word8 
             , registers :: STUArray s Word8 Word8 
             , memRefs :: STRef s MemRefs 
             , window :: Window 
             , renderer :: Renderer 
             } 
    
    data MemRefs = MemRefs { _sp :: Word16 
             , _pc :: Word16 
             , _cycles :: Word16 
             , _ime :: Bool --Interrupt Master Enable Flag 
             , _halt :: Bool --Are we halted or not 
             , _mode :: GPUMode -- GPU mode 
             , _line :: Word8 -- GPU line 
             , _transferred :: Bool 
             , _gpuCycles :: Word16 
             } 
    mkLenses ''MemRefs 
    

Dobrą rzeczą jest to, można teraz grupować MemRef typ al gusto, soczewki użytkowania wygodnie dotrzeć w głąb struktury. Ulepszając strukturę drzewa, aktualizacje staną się bardziej wydajne. (Prawdopodobnie również chcą unbox te Word16 i Bool pola, to naprawdę bardzo rozrzutny, aby utrzymać takie małe typy zapakowane.)

Nawet tak, to powinien być przygotowany, że to nie będzie działać tak szybko, jak podobnie kompleksowej realizacji w, powiedz, C++. Aby osiągnąć porównywalną wydajność, prawdopodobnie trzeba ręcznie przekazać ten stan, aby użyć pojedynczegoSTArray, w którym wszystkie informacje o stanie są zakodowane, i pisać brzydkie style i setery w stylu OO w ST, aby uczynić je zdalnie wygodnymi.

+0

Z punktu widzenia efektywności, 'STRef' obecnie jest do niczego takiego, podczas gdy' StateT BigRecord (ST s) 'jest bolesne, ale szybkie. – dfeuer

+0

Dziękujemy! Kontynuacja - dlaczego boks małych typów jest nieekonomiczny? Z tego, co widzę, wynika różnica między aktualizacją tylko pola _ime (na przykład), a świeżymi MemRefs za każdym razem, gdy aktualizuję dowolne z pól. –

+0

Jest to głównie marnotrawstwo pod względem pamięci (przy użyciu 64-bitowego wskaźnika do adresowania oddzielnego 16-bitowego wejścia do danych) i złe dla spójności pamięci podręcznej. Na szczęście nie musisz stosować się do tych wskazówek podczas aktualizowania _annego_ pola rekordów, ale wciąż jest tam sporo śmieci. Ale nie jestem pewien, jaki wpływ miałoby to na twój wniosek. – leftaroundabout