import { WindowManager } from '../wnd/window_manager' ; import { Wnd } from '../wnd/wnd' ; import { DomUtil } from '../util/dom_util' ; import { Nes } from '../nes/nes' ; import { Addressing , Instruction , OpType , kInstTable } from '../nes/cpu/inst' ; import { disassemble } from '../nes/cpu/disasm' ; import { Util } from '../util/util' ; import { WndEvent } from '../wnd/types' ; import { AppEvent } from './app_event' ; import * as Pubsub from '../util/pubsub' ; export class RegisterWnd extends Wnd { protected valueElems = new Array < HTMLInputElement > ( ) ; protected subscription : Pubsub . Subscription ; private static createPregStr ( p ) { const chrs = 'NV_BDIZC' ; const ss = new Array < string > ( 8 ) ; for ( let i = 0 ; i < 8 ; ++ i ) ss [ i ] = ( p & ( 0x80 >> i ) ) !== 0 ? chrs [ i ] : '.' ; return ss . join ( '' ) ; } public constructor ( wndMgr , protected nes , protected stream ) { super ( wndMgr , 100 , 160 , 'Regs' ) ; const content = this . createContent ( ) ; this . setContent ( content ) ; this . subscription = this . stream . subscribe ( type => { switch ( type ) { case AppEvent . Type . RESET : case AppEvent . Type . STEP : case AppEvent . Type . PAUSE : case AppEvent . Type . BREAK_POINT : this . updateStatus ( ) ; break ; } } ) ; wndMgr . add ( this ) ; } public updateStatus ( ) { const cpu = this . nes . getCpu ( ) ; const regs = cpu . getRegs ( ) ; this . valueElems [ 0 ] . value = Util . hex ( regs . pc , 4 ) ; this . valueElems [ 1 ] . value = Util . hex ( regs . a , 2 ) ; this . valueElems [ 2 ] . value = Util . hex ( regs . x , 2 ) ; this . valueElems [ 3 ] . value = Util . hex ( regs . y , 2 ) ; this . valueElems [ 4 ] . value = Util . hex ( regs . s , 2 ) ; this . valueElems [ 5 ] . value = RegisterWnd . createPregStr ( regs . p ) ; this . valueElems [ 6 ] . value = String ( this . nes . getCycleCount ( ) ) ; } public close ( ) { this . stream . triggerCloseWnd ( this ) ; this . subscription . unsubscribe ( ) ; super . close ( ) ; } protected createContent ( ) { const root = document . createElement ( 'div' ) ; root . className = 'fixed-font' ; const kElems = [ { name : 'PC' } , { name : 'A' } , { name : 'X' } , { name : 'Y' } , { name : 'S' } , { name : 'P' } , { name : 'cycle' } , ] ; const table = document . createElement ( 'table' ) ; DomUtil . setStyles ( table , { fontSize : '10px' , width : '100%' , } ) ; this . valueElems = new Array < HTMLInputElement > ( ) ; for ( let i = 0 ; i < kElems . length ; ++ i ) { const tr = document . createElement ( 'tr' ) ; table . appendChild ( tr ) ; const name = document . createElement ( 'td' ) ; name . innerText = kElems [ i ] . name ; tr . appendChild ( name ) ; const value = document . createElement ( 'td' ) ; tr . appendChild ( value ) ; const valueInput = document . createElement ( 'input' ) ; valueInput . className = 'fixed-font' ; valueInput . type = 'text' ; valueInput . style . width = '100%' ; value . appendChild ( valueInput ) ; this . valueElems . push ( valueInput ) ; } root . appendChild ( table ) ; return root ; } } export class RamWnd extends Wnd { private static RAM_SIZE = 0x800 ; private static MARGIN = 4 ; private static LINE_HEIGHT = 14 ; private static COL_NO_CHANGE = '' ; private static COL_CHANGED = '#c22' ; private subscription : Pubsub . Subscription ; private contentRoot : HTMLElement ; private cells : Array < HTMLElement > ; private memBuf1 : Uint8Array = new Uint8Array ( RamWnd . RAM_SIZE ) ; private memBuf2 : Uint8Array = new Uint8Array ( RamWnd . RAM_SIZE ) ; private dirty = false ; private visibleStartAdr = 0 ; private visibleEndAdr = 0 ; private paused = false ; public constructor ( wndMgr , private nes , private stream ) { super ( wndMgr , 420 , 236 , 'Ram' ) ; const { root , cells } = this . createContent ( ) ; this . setContent ( root ) ; this . contentRoot = root ; this . cells = cells ; this . addResizeBox ( ) ; this . updateStatus ( ) ; this . subscription = this . stream . subscribe ( type => { switch ( type ) { case AppEvent . Type . PAUSE : this . paused = true ; case AppEvent . Type . STEP : case AppEvent . Type . BREAK_POINT : this . updateStatus ( ) ; this . render ( 0 , RamWnd . RAM_SIZE ) ; break ; case AppEvent . Type . RESET : case AppEvent . Type . RENDER : this . updateStatus ( ) ; this . render ( ) ; break ; case AppEvent . Type . RUN : this . paused = false ; break ; } } ) ; this . dirty = true ; this . contentRoot . addEventListener ( 'scroll' , _ => { if ( this . paused ) { this . updateVisibleArea ( ) ; this . render ( ) ; } else { this . dirty = true ; } } ) ; } public onEvent ( event , _param ? ) { switch ( event ) { case WndEvent . RESIZE_END : this . updateVisibleArea ( ) ; this . dirty = false ; break ; default : break ; } } private updateStatus ( ) { const bus = this . nes . getBus ( ) ; const prev = this . memBuf1 ; const curr = this . memBuf2 ; for ( let i = 0 ; i < RamWnd . RAM_SIZE ; ++ i ) curr [ i ] = bus . read8 ( i ) ; this . memBuf1 = curr ; this . memBuf2 = prev ; } private render ( startAdr = this . visibleStartAdr , endAdr = this . visibleEndAdr ) { if ( this . dirty ) { this . updateVisibleArea ( ) ; this . dirty = false ; } const curr = this . memBuf1 ; const prev = this . memBuf2 ; for ( let i = startAdr | 0 , end = endAdr | 0 ; i < end ; ++ i ) { const cell = this . cells [ i ] ; const x = curr [ i ] ; const hh = Util . hex ( x , 2 ) ; if ( cell . innerText !== hh ) cell . innerText = hh ; const bg = x === prev [ i ] ? RamWnd . COL_NO_CHANGE : RamWnd . COL_CHANGED ; if ( cell . style . backgroundColor !== bg ) cell . style . backgroundColor = bg ; } } private updateVisibleArea ( ) { const rc = this . contentHolder . getBoundingClientRect ( ) ; const top = this . contentRoot . scrollTop ; const start = Math . floor ( ( top - RamWnd . MARGIN ) / RamWnd . LINE_HEIGHT ) | 0 ; const end = Math . floor ( ( top + rc . height - RamWnd . MARGIN ) / RamWnd . LINE_HEIGHT ) | 0 ; this . visibleStartAdr = Util . clamp ( start * 16 , 0 , RamWnd . RAM_SIZE ) ; this . visibleEndAdr = Util . clamp ( ( end + 1 ) * 16 , 0 , RamWnd . RAM_SIZE ) ; } public close ( ) { this . stream . triggerCloseWnd ( this ) ; this . subscription . unsubscribe ( ) ; super . close ( ) ; } private createContent ( ) { const root = document . createElement ( 'div' ) ; root . className = 'ramwnd full-size' ; const scroll = document . createElement ( 'div' ) ; scroll . className = 'fixed-font' ; DomUtil . setStyles ( scroll , { overflowX : 'hidden' , margin : `${ RamWnd . MARGIN }px` , whiteSpace : 'nowrap' , } ) ; const lineDivs = new Array < HTMLElement > ( ) ; const cells = new Array < HTMLElement > ( ) ; for ( let i = 0 ; i < RamWnd . RAM_SIZE / 16 ; ++ i ) { const line = document . createElement ( 'div' ) ; DomUtil . setStyles ( line , { height : `${ RamWnd . LINE_HEIGHT }px` , } ) ; scroll . appendChild ( line ) ; lineDivs . push ( line ) ; line . appendChild ( document . createTextNode ( `${ Util . hex ( i * 16 , 4 ) }: ` ) ) ; for ( let j = 0 ; j < 16 ; ++ j ) { if ( j > 0 ) line . appendChild ( document . createTextNode ( ' ' ) ) ; const cell = document . createElement ( 'span' ) ; cell . innerText = '00' ; line . appendChild ( cell ) ; cells . push ( cell ) ; } } root . appendChild ( scroll ) ; return { root , cells } ; } } const MAX_BYTES = 3 ; const MAX_LINE = 100 ; const kIllegalInstruction = { opType : OpType . UNKNOWN , addressing : Addressing . UNKNOWN , bytes : 1 , cycle : 0 , } ; export class TraceWnd extends Wnd { private textarea : HTMLTextAreaElement ; private mem = new Uint8Array ( MAX_BYTES ) ; private bins = new Array < string > ( MAX_BYTES ) ; private lines = new Array < string > ( ) ; private subscription : Pubsub . Subscription ; public constructor ( wndMgr , private nes , private stream ) { super ( wndMgr , 400 , 160 , 'Trace' ) ; const content = this . createContent ( ) ; this . setContent ( content ) ; this . reset ( ) ; this . subscription = this . stream . subscribe ( type => { switch ( type ) { case AppEvent . Type . RESET : this . reset ( ) ; case AppEvent . Type . STEP : case AppEvent . Type . PAUSE : case AppEvent . Type . BREAK_POINT : this . updateStatus ( ) ; break ; } } ) ; wndMgr . add ( this ) ; } public reset ( ) { this . lines = [ ] ; } public updateStatus ( ) { const cpu = this . nes . getCpu ( ) ; const bus = this . nes . getBus ( ) ; const pc = cpu . getRegs ( ) . pc ; const op = bus . read8 ( pc ) ; const inst = kInstTable [ op ] || kIllegalInstruction ; for ( let i = 0 ; i < inst . bytes ; ++ i ) { const m = bus . read8 ( pc + i ) ; this . mem [ i ] = m ; this . bins [ i ] = Util . hex ( m , 2 ) ; } for ( let i = inst . bytes ; i < MAX_BYTES ; ++ i ) this . bins [ i ] = '  ' ; const pcStr = Util . hex ( pc , 4 ) ; const binStr = this . bins . join ( ' ' ) ; const asmStr = disassemble ( inst , this . mem , 1 , pc ) ; this . putConsole ( `${ pcStr }: ${ binStr }   ${ asmStr }` ) ; } public close ( ) { this . stream . triggerCloseWnd ( this ) ; this . subscription . unsubscribe ( ) ; super . close ( ) ; } private createContent ( ) { const root = document . createElement ( 'div' ) ; const textarea = document . createElement ( 'textarea' ) ; textarea . className = 'fixed-font' ; DomUtil . setStyles ( textarea , { fontSize : '14px' , width : '100%' , height : '160px' , resize : 'none' , margin : '0' , padding : '2px' , border : 'none' , boxSizing : 'border-box' , } ) ; root . appendChild ( textarea ) ; this . textarea = textarea ; return root ; } private putConsole ( line ) { this . lines . push ( line ) ; if ( this . lines . length > MAX_LINE ) this . lines . shift ( ) ; this . textarea . value = this . lines . join ( '\n' ) ; this . textarea . scrollTop = this . textarea . scrollHeight ; } } export class ControlWnd extends Wnd { private stepBtn : HTMLButtonElement ; private runBtn : HTMLButtonElement ; private pauseBtn : HTMLButtonElement ; private subscription : Pubsub . Subscription ; public constructor ( wndMgr , private stream ) { super ( wndMgr , 192 , 32 , 'Control' ) ; const content = this . createElement ( ) ; this . setContent ( content ) ; this . updateState ( true ) ; this . subscription = this . stream . subscribe ( type => { switch ( type ) { case AppEvent . Type . RUN : this . updateState ( false ) ; break ; case AppEvent . Type . PAUSE : case AppEvent . Type . BREAK_POINT : this . updateState ( true ) ; break ; } } ) ; wndMgr . add ( this ) ; } public updateState ( paused ) { this . stepBtn . disabled = ! paused ; this . runBtn . disabled = ! paused ; this . pauseBtn . disabled = paused ; } public close ( ) { this . stream . triggerCloseWnd ( this ) ; this . subscription . unsubscribe ( ) ; super . close ( ) ; } private createElement ( ) { const root = document . createElement ( 'div' ) ; DomUtil . setStyles ( root , { width : '256px' , height : '32px' , } ) ; this . stepBtn = document . createElement ( 'button' ) ; this . stepBtn . innerText = 'Step' ; this . stepBtn . addEventListener ( 'click' , ( ) => { this . stream . triggerStep ( ) ; } ) ; root . appendChild ( this . stepBtn ) ; this . runBtn = document . createElement ( 'button' ) ; this . runBtn . innerText = 'Run' ; this . runBtn . addEventListener ( 'click' , ( ) => { this . stream . triggerRun ( ) ; } ) ; root . appendChild ( this . runBtn ) ; this . pauseBtn = document . createElement ( 'button' ) ; this . pauseBtn . innerText = 'Pause' ; this . pauseBtn . addEventListener ( 'click' , ( ) => { this . stream . triggerPause ( ) ; } ) ; root . appendChild ( this . pauseBtn ) ; const resetBtn = document . createElement ( 'button' ) ; resetBtn . innerText = 'Reset' ; resetBtn . addEventListener ( 'click' , ( ) => { this . stream . triggerReset ( ) ; } ) ; root . appendChild ( resetBtn ) ; return root ; } }