+adamantyr Posted February 9, 2018 Share Posted February 9, 2018 I was looking over an old game in a hex editor recently, and I noticed something interesting with the text data embedded in the program. All of the strings seemed fine until the end character, which was a byte value of 128 or greater. I realized what the program was doing was using the top bit on ASCII characters to determine the string end. This lets you store strings at their exact size, rather than one byte more to store either a length byte or a null terminator byte. Very clever! So I thought, how to leverage this on the TI, where I've spent a considerable amount of effort building menus and interfaces that consumed a lot of memory because of having to store strings and their addresses and lengths? So I write a routine, VTEXT, and it's companion, VTEXTC. It's a subroutine that takes the address on screen into R0, and a pointer to the desired text in R1. No length needed! It writes to the screen until it encounters the eighth bit, and stops. VTEXTC is the same, except it doesn't alter the VDP address. This will allow you to write concurrent strings to the screen one after the other. Advantages are: - Your text strings take up exactly their length in space - Reduced operations for plotting text to screen Disadvantages are: - All static text in source files has to end with the top bit set, which can be aggravating to figure out the value of a given ASCII character - Slower than a straight VMBW due to the need for a COC and copy operation on every character - If you don't have the top bit set, it could overrun the screen buffer and the rest of VDP Future expansion may include the idea of "values". Using characters 0-31 can be used as an indicator to, for example, switch to a stored numeric value in text form, so you can introduce string formatting. "%0 takes %1 damage!" for example, so it knows to plug in the 0 and 1 pre-calculated values into the string as it's writing to the screen. TOPBIT DATA >8000 * Top bit word VDPWA EQU >8C02 * VDP Write address port VDPRD EQU >8800 * VDP Read address port VDPWD EQU >8C00 * VDP access port (input/output) SCRADR EQU >0000 * Screen address TXTLN1 TEXT 'This is sample tex' BYTE 244 TEXT 'More sample tex' BYTE 244 TEXT 'Sample Tex' BYTE 244 TEXT '-Sample Tex' BYTE 244 * Video Text writer, CPU to VDP * Uses only R0 and R1 for location, length is determined by the top bit * Sends R1 value back to calling rouine for continuous stream of text * VTEXTC does not update position in VDP, so you can write multiple strings concurrently VTEXT ORI R0,>4000 * Set address for VDP write SWPB R0 * Swap to low byte MOVB R0,@VDPWA * Move to VDP address SWPB R0 * Swap MOVB R0,@VDPWA * Move to VDP address ANDI R0,>3FFF * remove extra bit so address is preserved for subsequent calls VTEXTC MOVB *R1+,R2 * Copy character to R2 COC @TOPBIT,R2 * Check if top bit is set (end of line indicator) JEQ VTEXT1 * If so, skip to end MOVB R2,@VDPWD * Write character to screen JMP VTEXTC * Loop VTEXT1 ANDI R2,>7F00 * Reset top bit on character MOVB R2,@VDPWD * Write to screen RT * return to calling program * Example LI R0,SCRADR+128 * Set R0 to SCRADR+128 LI R1,TXTLN1 * Set R1 to start of text BL @VTEXT * Write string to screen AI R0,32 * Add 32 to screen position BL @VTEXT * Write a second line of the text LI R0,SCRADR+256 * Change R0 to a different position BL @VTEXT * Write next line to screen BL @VTEXTC * Write the next text segment immediately after the prior * Program continues... Quote Link to comment Share on other sites More sharing options...
+mizapf Posted February 9, 2018 Share Posted February 9, 2018 Usually, one would use an additional null character to terminate; this is also used in MDOS on the Geneve. So the effect of this approach is to save one byte per string, right? Quote Link to comment Share on other sites More sharing options...
apersson850 Posted February 9, 2018 Share Posted February 9, 2018 Yes, this is to remove the need for a text length byte or a terminator byte. Which is one byte per string, unless you allow strings longer than 255 bytes, in which case the leading length byte has to be a leadning length word. Quote Link to comment Share on other sites More sharing options...
+mizapf Posted February 9, 2018 Share Posted February 9, 2018 Is it worth it? A null terminator would allow using the remaining 128 characters after ASCII 127. Also, it would perform better, because you could simply check for null when fetching the character. Quote Link to comment Share on other sites More sharing options...
Tursi Posted February 9, 2018 Share Posted February 9, 2018 Nice! This is how many Apple2 programs handled their text strings (maybe the ROMs, I don't recall). You can optimize your test a little bit: COC @TOPBIT,R2 * Check if top bit is set (end of line indicator) JEQ VTEXT1 * If so, skip to end Can be replaced with "JLT VTEXT1"... since a byte with the MSB set is a negative value, and the MOVB will set the flags. Saves you the COC, which is one of the more expensive parts of that loop. Quote Link to comment Share on other sites More sharing options...
Stuart Posted February 9, 2018 Share Posted February 9, 2018 A similar trick is used with the error message strings in Cortex BASIC, but it negates the final byte in each string (which sets the MS bit so JLT/JGT jumps can be used). The required character is recovered by negating the value again. Quote Link to comment Share on other sites More sharing options...
+adamantyr Posted February 9, 2018 Author Share Posted February 9, 2018 Nice! This is how many Apple2 programs handled their text strings (maybe the ROMs, I don't recall). You can optimize your test a little bit: COC @TOPBIT,R2 * Check if top bit is set (end of line indicator) JEQ VTEXT1 * If so, skip to end Can be replaced with "JLT VTEXT1"... since a byte with the MSB set is a negative value, and the MOVB will set the flags. Saves you the COC, which is one of the more expensive parts of that loop. Very nice optimization, that does save some bytes and a costly memory to register comparison! Quote Link to comment Share on other sites More sharing options...
+adamantyr Posted February 9, 2018 Author Share Posted February 9, 2018 Is it worth it? A null terminator would allow using the remaining 128 characters after ASCII 127. Also, it would perform better, because you could simply check for null when fetching the character. Well if you are participating in, for example, a 4K RAM contest, every byte counts. 2 Quote Link to comment Share on other sites More sharing options...
+adamantyr Posted February 10, 2018 Author Share Posted February 10, 2018 I think a big part of why I like this particular technique is that using a whole byte as a null terminator for text is SUCH a waste. I'd written a lot of my CRPG interface code using the standard VMBW technique with three registers, but now I look at that and just think "That's FOUR BYTES i'm wasting every time I write a single line of text." Yes, I'm using AMS but I'm still trying to keep my modules to a fixed size. My Start module now has less than a kilobyte of space, and I still have to write up the opening introduction code. So optimization techniques like this are still useful. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.