M/C Part 28 TOTAL RECALL ------------ Last time round I described how Driver allocates memory in 16k pages, a practice which is pretty obvious given the physical specifications of the Coupe. However, since the user can switch between applications willy nilly, and some applications won't know how much memory they need until it runs out (for example, a word processor), the allocation inevitably leads to memory being broken up and incongruent. I also mentioned the application page table (APT): How you initialise it, how it stores page numbers, and how to reserve/ free pages. I won't bother to repeat all that; you can just look it up. However, the incongruence of the pages can lead to problems - you've got to be careful not to touch memory in section D (paging in ROM1 could help), and shuffling data about can take ages! This was precisely the nightmare I had with Notepad - the word processor you get in the Driver package. I designed it so that typing automatically inserted characters and deleting automatically pulled all the following text backwards. In addition I wanted to use the Driver clipboard, deleting and inserting large blocks of text. This meant that either A. I could restrict myself to files 16k long OR B. I had to find a fast way of copying all the text. Ugh! The first option was attractive - I didn't have to worry about 19-bit address representation (page/offset), and I could use LDIR and LDDR for simplicity. But it seemed a waste of Driver's facilities, and I wanted a SAM word processor that could handle large files. Unfortunately, the second option wouldn't work - even in a file 30k long, inserting characters at the start caused noticable delays. So what did I do? What I always seem to do - I found a compromise that was better than either alternative. And I'm gonna tell you about it... PAGE BUFFERING -------------- I mentioned that I could handle files up to about 16k long without problems. So, I treated the file in blocks about 16k long - just the way it is represented by the paging mechanism. The sneaky bit is that I left about 256 bytes free at the end of each page, so that the default length of a file block was actually 15.75k long. So, when the user is typing, the longest block the program has to shuffle (normally) is 15.75k. As this happens, the block size increases until it reaches 16k, then the top 256 bytes are copied into the bottom of the next block, and any overspill from that is copied into the next one... and so on. Obviously, when the user deletes the block size is reduced. The method means that we need a table of pointers for the top of each block, and some wee routines to adjust any address that points to a non-existant character. We also need routines to insert and delete a block of bytes. ADJUSTING THE PAGING WHILST COUNTING ------------------------------------ This is the first routine I'm gonna give you. We can represent each address with a page number (0-127) in A, and an address offset (8000h-BFFFh) in HL. Put the table of length pointers at an address which is a multiple of 512. Use the routine after something like INC HL when the paging might need changed - it returns the altered address in AHL, with CY if the physical paging needs to be changed. page.up PUSH DE PUSH HL PUSH HL LD L,A LD H,length.tab/512 ADD HL,HL LD E,(HL) INC L LD D,(HL) POP HL SBC HL,DE ; NC if we need to increase ; the paging. POP HL POP DE CCF ; Toggle CY. RET NC LD HL,8000h INC A RET Of course, this can slow things up quite considerably when you're shuffling data about, so it can be a good idea to make a note of the block length when you start, and only update it when you change blocks. Counting downwards is easier, because the bottom address is always 8000h: Entry and exit as before. page.down CP A BIT 7,H RET NZ DEC A PUSH DE LD L,A LD H,length.tab/512 ADD HL,HL LD E,(HL) INC L LD D,(HL) LD L,E LD H,D POP DE DEC HL SCF RET You might want to vary things a bit to compensate for empty pages in the middle and so on, but it all depends on the application. INSERTING A BLOCK ----------------- This insert routine is horrendously complicated (heh, heh, heh), considering that all it does it move some data around in the same way that an LDDR command would. You need a buffer 256 bytes long, at a 0-lsb address. In addition, it can only cope with blocks up to 16383 bytes long - for longer blocks use multiple calls. So why is it so long? (A question I'm often asked. Ahem). Well, I don't really know. It was by far the most difficult thing I had to do for Notepad, and took well over a week to get working. If the insert is going to cause a page overspill, the block length is reset at 15.75k - of course, the routine has to cope with the possibility that more than one overspill will result. Extra memory is reserved from Driver if necessary, and an error reported if there are any problems ("nomem"). The only other routines needed are: driver_in Page Driver into section C data_in Page data page A into section C. add_ix_a Adds A to IX: Result in IX, A unchanged. Take a deep breath... ; ENTRY: AHL = address, DE = length of block. insert.block LD (ib.page),A ; Store for later LD (ib.address),HL ADD A,A LD IX,length.tab ; Page length table CALL add_ix_a LD L,(IX+0) LD H,(IX+1) ; Page "top" in HL ADD HL,DE ; Add on block length BIT 6,H ; Page overspill? LD A,(ib.page) LD HL,(ib.address) JP Z,small.insert ; If not, use a simpler ; routine. ib1 CALL ib.check ; If page overspill: Returns CY, HL = 8000h, A incremented, DE = space needed in next page. ; If no overspill: DE = new end of page, HL = old, A unchanged. JR C,ib1 ; Loop until we've dealt ; with the overspill LD (ib.countp1),A ; Store pointers for ; source/target addresses. LD (ib.countp2),A LD (ib.counta1),HL LD (ib.counta2),DE ADD A,A LD IX,length.tab CALL add_ix_a LD (IX+0),E LD (IX+1),D ; Store new page "top" LD A,(ib.page) LD C,A ; Page for start of block ib2 LD A,(ib.countp1) LD HL,(ib.counta1) CALL data_in EX AF,AF' LD DE,buffer ib3 EX AF,AF' CP C ; Reached end page ; (start!)? JR NZ,ib4 ; If not... PUSH BC PUSH HL LD BC,(ib.address) SBC HL,BC ; Compare addresses POP HL POP BC JR Z,ib.end ; Finish main loop if end ib4 DEC HL BIT 7,H ; Page overlap? JR NZ,ib7 ; If not, jump DEC A PUSH AF PUSH IX ADD A,A LD IX,length.tab CALL add_ix_a LD L,(IX+0) LD H,(IX+1) ; Get top address LD (IX+0),0 LD (IX+1),BFh ; Store balance size POP IX POP AF CALL data_in EX AF,AF' JR ib3 ; Loop back for empty page ib7 EX AF,AF' LD A,(HL) ; Copy byte to buffer DEC E LD (DE),A JR NZ,ib3 ; Loop for 256 bytes EX AF,AF' LD (ib.countp1),A ; Store pointers LD (ib.counta1),HL LD A,(ib.countp2) LD DE,(ib.counta2) ; Target pointers LD HL,buffer CALL data_in EX AF,AF' ib5 DEC DE BIT 7,D ; Page overlap? JR NZ,ib6 ; If not... EX AF,AF' DEC A ; Next page LD DE,BF00h ; Balance address DEC IX DEC IX LD (IX+0),E LD (IX+1),D ; Store balance CALL data_in EX AF,AF' JR ib5 ; Loop for empty page ib6 DEC L LD A,(HL) ; Copy byte from buffer LD (DE),A JR NZ,ib5 ; Loop for 256 bytes EX AF,AF' LD (ib.countp2),A ; Store pointers LD (ib.counta2),DE JP ib2 ; Loop until finished ib.end XOR A ; E = bytes left to copy SUB E JR Z,ib.e3 ; If none, jump LD B,A ; B = 256-E LD A,(ib.countp2) ; Target pointers LD DE,(ib.counta2) LD HL,buffer CALL data_in EX AF,AF' ib.e1 DEC DE BIT 7,D JR NZ,ib.e2 ; Page overlap? EX AF,AF' LD DE,BF00h DEC IX DEC IX LD (IX+0),E LD (IX+1),D ; Store balance JR ib.e1 ib.e2 DEC L LD A,(HL) LD (DE),A ; Copy from buffer DJNZ ib.e1 ; Loop. EX AF,AF' LD (ib.countp2),A ib.e3 LD HL,(ib.address) LD BC,BF00h CP A SBC HL,BC RET C RET Z ; Ret if start ad