+yetanothertroll Posted May 30 Share Posted May 30 (edited) I've been experimenting quite a bit lately with the Windows hosted Fastbasic 4.6 crosscompiler. Targeting cart images makes it a breeze to test them out in Altirra, much less pointy-clicky than having to import a .XEX into a scratch .ATR or fooling with virtual filesystems. Hopefully this doesn't bite me someday! DIMming large arrays and then starting some memory-hungry graphics modes like 11 or 9 results in unexpected behavior instead of returning to DOS or Memo Pad. Sometimes the display shows garbage, other times it's just black, still other times the (edit: emulated Atari) computer appears to hang. Of course calling DIM after setting up the graphics mode will reasonably bounce out. Use BYTE arrays if you possibly can, unless being unable to use INC or DEC on array elements tips the CPU cycle balance back towards the default integer arrays. Numeric expressions like ABS(CI% * (CR% + CR%)) + CI% are faster than ABS((CR% + CR%) * CI%) + CI% is faster than 2 * ABS(CI% * CR%) + CI% is faster than ABS(CI% * CR%) * 2 + CI%. Would CI% + Abs(CI% * (CR% + CR%)) make a difference? Who knows, the ship is already burning Unlike in some other Basics, the COLOR statement is very fast. There's hardly any need at all to make min- or max-heaps of objects to be redrawn just to keep calls to COLOR to a bare minimum I don't understand FILL TO at all. Maybe it just doesn't like GTIA modes? EDIT: The floating point weirdness is probably at least partly due to the OS-B mathpack madness. It seems it's sensitive to whether ABS(x%) < ABS(y%) in x% +/- y%, x% * y%, maybe others Edited May 30 by yetanothertroll Going mad from the revelation Quote Link to comment Share on other sites More sharing options...
+yetanothertroll Posted May 31 Author Share Posted May 31 How do I prevent or at least delay memory fragmentation issues with re-DIMming arrays? I have something like this in my main loop, If DecayMax <= DecayHighCount DecayMax = 4 + DecayHighCount + DecayHighCount / 4 EndIf Dim DecayPixelX(DecayMax - 1) Byte, DecayPixelY(DecayMax - 1) Byte Dim DecayTarget(DecayMax - 1) Byte, DecayCurrent(DecayMax - 1) ' Byte DecayCount = 0 DecayHighCount = 0 After a few repetitions of that, FB quits due to memory issues. I made certain to allocate those arrays last. I don't want to use CLR and lose all my other variables or have to save and reload them from disk or some memory block somewhere I have to manage myself. Is there a good way to erase individual arrays to try to get a nice contiguous block of free memory for reallocating them? Quote Link to comment Share on other sites More sharing options...
dmsc Posted June 1 Share Posted June 1 Hi! 5 hours ago, yetanothertroll said: How do I prevent or at least delay memory fragmentation issues with re-DIMming arrays? I have something like this in my main loop, If DecayMax <= DecayHighCount DecayMax = 4 + DecayHighCount + DecayHighCount / 4 EndIf Dim DecayPixelX(DecayMax - 1) Byte, DecayPixelY(DecayMax - 1) Byte Dim DecayTarget(DecayMax - 1) Byte, DecayCurrent(DecayMax - 1) ' Byte DecayCount = 0 DecayHighCount = 0 After a few repetitions of that, FB quits due to memory issues. I made certain to allocate those arrays last. I don't want to use CLR and lose all my other variables or have to save and reload them from disk or some memory block somewhere I have to manage myself. Is there a good way to erase individual arrays to try to get a nice contiguous block of free memory for reallocating them? Well, due to space constraints, FastBasic does not implement any means to free blocks of memory. My suggestion is to simply DIM the arrays at the start of the program with the maximum expected value for "DecayMax", it will be simpler and faster. Have Fun! 1 Quote Link to comment Share on other sites More sharing options...
+yetanothertroll Posted June 1 Author Share Posted June 1 (edited) 1 hour ago, dmsc said: Hi! Well, due to space constraints, FastBasic does not implement any means to free blocks of memory. My suggestion is to simply DIM the arrays at the start of the program with the maximum expected value for "DecayMax", it will be simpler and faster. Have Fun! That's actually going to complicate things for this particular project. If DecayMax doesn't change, do repeated calls to Dim still leak memory? Thanks! EDIT: Would DIY-ing something like an array deallocation proc be possible? How does the array allocator work currently? Which source files should I be looking for? Edited June 1 by yetanothertroll I just had an idea, a bad idea, a very very bad idea Quote Link to comment Share on other sites More sharing options...
tebe Posted June 1 Share Posted June 1 5 hours ago, yetanothertroll said: EDIT: Would DIY-ing something like an array deallocation proc be possible? How does the array allocator work currently? Which source files should I be looking for? you expect too high a level of abstraction XLOS with Rapidus gives you the ability to manage memory http://drac030.krap.pl/pl-specyfikacja.php 1 Quote Link to comment Share on other sites More sharing options...
+yetanothertroll Posted June 1 Author Share Posted June 1 (edited) 7 hours ago, tebe said: you expect too high a level of abstraction XLOS with Rapidus gives you the ability to manage memory http://drac030.krap.pl/pl-specyfikacja.php No problem, now that I know that's what's actually going on. I'll figure something out for this project and then think about how to design a malloc/free/realloc like system with the minimum possible overhead. Two bytes each for the start and end of the dynamic memory arena plus another two bytes for each memory chunk? If memory is managed on two or four byte boundaries, the low order bits can flag freed/in use and such. If a flag is kept for whether free has ever been used, blocks can be allocated quickly with no need to step through the block list looking for a previously freed block. Contiguous free blocks can be merged at this stage. I'm not actually expecting anyone to actually add all this, this is merely a thought experiment lol Edited June 1 by yetanothertroll Double checking to whom I'm replying 🤦🏻♂️ Quote Link to comment Share on other sites More sharing options...
dmsc Posted June 2 Share Posted June 2 Hi! 23 hours ago, yetanothertroll said: That's actually going to complicate things for this particular project. If DecayMax doesn't change, do repeated calls to Dim still leak memory? Yes I still do not understand why you need to change the size of the array. The 8-bit computers have a small amount of memory available, so you need to plan ahead how to use each byte , setting the size to a large value should work. 23 hours ago, yetanothertroll said: EDIT: Would DIY-ing something like an array deallocation proc be possible? How does the array allocator work currently? Which source files should I be looking for? Works the same as other 8-bit BASICs, it simply has a base pointer and a top pointer. When you DIM an array or allocate a string, the top pointer is used as the address of the new allocation, and the top pointer is moved up. There are BASICs that support garbage collection - when the top pointer reach the top of available memory, all the objects are compacted and the top pointer recalculated. But that means that existing strings and arrays change address, this means that you can't store the address to use later, and this breaks a lot of programs that store data or machine code in strings. Implementing this in FastBasic also has another complication - that at runtime there is no information on the type of variables, so you don't know which variables are pointer types (arrays or strings) to correctly adjust the values at GC - this is extra complicated because string-arrays are actually an array of pointers. An idea would be to transform each allocated block into a structure with: 2 bytes for the block size, 2 bytes for the address of the variable that points to the block, n bytes for the actual data. This transforms the area into a linked-list, because you can walk the allocations by doing "next_ptr = ptr + ptr->size", and you can check which variable holds your address by looking at "ptr->var". So, a FastBasic with GC would need: - Changing the "alloc_array" code (at src/interp/clearmem.asm) to allocate 4 extra bytes, and store at the beginning of each block the block size. - Change all the "alloc_array" callers to store the address of the variable on which the value will be stored to the block. - On allocation failure at "err_nomem", call the GC, which does: - Set "ptr" and "new_ptr" to the address of the first block. - Iterate through the list of blocks, until "ptr" is equal to topmem. - For each block, check if *(ptr->var) contains the address of the block, if not the block is free to reuse. - If the block was used: - Adjust *(ptr->var) with new_ptr (the address of the block after the move). - Move the block data from ptr to new_ptr. - Set "new_ptr" to "new_ptr + ptr->size" - Set "ptr" to "ptr + ptr->size" to go to next block. - At the end, set topmem to the value of new_ptr (this is the end of memory). Does not seems that difficult. Have Fun! Quote Link to comment Share on other sites More sharing options...
+yetanothertroll Posted June 2 Author Share Posted June 2 (edited) 1 hour ago, dmsc said: Hi! Yes I still do not understand why you need to change the size of the array. The 8-bit computers have a small amount of memory available, so you need to plan ahead how to use each byte , setting the size to a large value should work. Works the same as other 8-bit BASICs, it simply has a base pointer and a top pointer. When you DIM an array or allocate a string, the top pointer is used as the address of the new allocation, and the top pointer is moved up. There are BASICs that support garbage collection - when the top pointer reach the top of available memory, all the objects are compacted and the top pointer recalculated. But that means that existing strings and arrays change address, this means that you can't store the address to use later, and this breaks a lot of programs that store data or machine code in strings. Implementing this in FastBasic also has another complication - that at runtime there is no information on the type of variables, so you don't know which variables are pointer types (arrays or strings) to correctly adjust the values at GC - this is extra complicated because string-arrays are actually an array of pointers. [ scary stuff elided ] Does not seems that difficult. Have Fun! A full mark and sweep GC? No thanks. If I wanted random two second freezes while the GC churns all the RAM, I'd just fire up Logo! I was thinking more along the lines of the old C malloc routines, which can reuse previously freed blocks without any GC sweep. Also, if an array is Dim'ed already, if it's Dim'ed again, the metadata for the previous size, address, etc., if any, are still available to the second call to Dim, yes? EDIT: I forgot to mention why I might want to expand certain arrays, with or without preserving their contents. 99% of the time, the arrays only need to be small, around 36 elements or so. However, there are some edge cases that will need more like 90-100 or even 3-400, but if I always allocate the maximum "just in case," that somehow doesn't sit well with me 🤷♂️ Edited June 2 by yetanothertroll Oops, forgot half my point Quote Link to comment Share on other sites More sharing options...
dmsc Posted June 3 Share Posted June 3 Hi! 21 hours ago, yetanothertroll said: A full mark and sweep GC? No thanks. If I wanted random two second freezes while the GC churns all the RAM, I'd just fire up Logo! Well, at least the GC will only run when you actually allocate memory. By the way, this is how Microsoft Basic works in the C64 and also in the Atari. It is not as slow, because you can assume that the number of memory blocks is always small. In Microsoft Basic, calls to "STR$()", "MID$", etc allocate new temporary strings that are left as garbage, and need to be reclaimed later. FastBasic, like Atari BASIC, uses fixed memory addresses for STR$ and CHR$, this is faster but means that you can't use STR$() twice in the same expression, for example this: "? STR$(1)<>STR$(0)" will print 0 both in FastBasic and Atari BASIC. 21 hours ago, yetanothertroll said: I was thinking more along the lines of the old C malloc routines, which can reuse previously freed blocks without any GC sweep. Also, if an array is Dim'ed already, if it's Dim'ed again, the metadata for the previous size, address, etc., if any, are still available to the second call to Dim, yes? Not currently - FastBasic does not stores the size of an array, and as you can see from the source, DIM receives the size of the array in bytes, allocates memory and then stores in the variable. In FastBasic, arrays and strings are pointers, the same size as integers, and stored in registers. The size of the allocated area would need to be stored inside the allocated area itself. 21 hours ago, yetanothertroll said: EDIT: I forgot to mention why I might want to expand certain arrays, with or without preserving their contents. 99% of the time, the arrays only need to be small, around 36 elements or so. However, there are some edge cases that will need more like 90-100 or even 3-400, but if I always allocate the maximum "just in case," that somehow doesn't sit well with me 🤷♂️ Well, there is no other use for the memory really, as no other program will run concurrently , so it is not "wasted". Have Fun! 1 1 Quote Link to comment Share on other sites More sharing options...
+yetanothertroll Posted June 4 Author Share Posted June 4 I think part of my issue was I misunderstood how and when drivers/handlers for peripherals like the CX85 keypad and 850 interface module are loaded, sometimes from boot disk, sometimes from the peripheral's own firmware, so I figured I'd need to play nice and worry about hogging too much memory when my program started up. But I also do have some ideas for a fairly fast and lightweight dynamic memory management system. Also, this might be useful for timing things longer than 18-20 minutes or so, ' Result will be in LongTime% Proc LongTime Repeat Long20A = Peek(20) Long19A = Peek(19) Long18A = Peek(18) Long18B = Peek(18) Long19B = Peek(19) Long20B = Peek(20) Until (Long18A = Long18B) And (Long19A = Long19B) and (Long20A <= Long20B) LongTime% = 256.0 LongTime% = ((Long18A * LongTime%) + Long19A) * LongTime% + Long20A EndProc Next, figuring out P/M sprites and how they interact with GTIA modes, wish me luck lol Quote Link to comment Share on other sites More sharing options...
dmsc Posted June 4 Share Posted June 4 Hi! 14 hours ago, yetanothertroll said: I think part of my issue was I misunderstood how and when drivers/handlers for peripherals like the CX85 keypad and 850 interface module are loaded, sometimes from boot disk, sometimes from the peripheral's own firmware, so I figured I'd need to play nice and worry about hogging too much memory when my program started up. But I also do have some ideas for a fairly fast and lightweight dynamic memory management system. Yes, when your program is running, it will use all available memory. 14 hours ago, yetanothertroll said: Also, this might be useful for timing things longer than 18-20 minutes or so, ' Result will be in LongTime% Proc LongTime Repeat Long20A = Peek(20) Long19A = Peek(19) Long18A = Peek(18) Long18B = Peek(18) Long19B = Peek(19) Long20B = Peek(20) Until (Long18A = Long18B) And (Long19A = Long19B) and (Long20A <= Long20B) LongTime% = 256.0 LongTime% = ((Long18A * LongTime%) + Long19A) * LongTime% + Long20A EndProc The current development version of FastBasic has a 'TIME%' function that returns the same as your proc, you can download it from the bottom of this page: https://github.com/dmsc/fastbasic/actions/runs/8963403908 14 hours ago, yetanothertroll said: Next, figuring out P/M sprites and how they interact with GTIA modes, wish me luck lol 🙂 You can start with the simple P/M demo at https://github.com/dmsc/fastbasic/blob/master/samples/int/pmtest.bas and change the graphics mode at the first line. Have Fun! 1 Quote Link to comment Share on other sites More sharing options...
+yetanothertroll Posted June 4 Author Share Posted June 4 Here's something fun, simple arithmetic with bools is slower than an If/Else, with executing the Else being the fastest. All the array stuff is meant to more or less mirror what's going on in the actual code I'm trying to speed up a bit. In the actual code, the equivalent to X will normally be 15 or 16, TempWord(j) will be between 0 and X, and the code will be run up to 15 or 16 times per screen pixel, Get K Print "Bools fools K = "; K; " "; 0 < 1; " "; 1 < 1; " "; Dim TempByte(3) Byte, TempWord(3) Word X = 65 TempWord(0) = K : TempWord(1) = K TempWord(2) = K : TempWord(3) = K Timer For i = 1 To 10000 j = i & 3 TempByte(j) = 3 - (TempWord(j) < X) ' True = 1 ' 720 jiffies ' If TempWord(j) < X ' TempByte(j) = 2 ' getting here 706 ' Else ' TempByte(j) = 3 ' 688 ' EndIf Next j = Time Print j Yup, you guessed it, I'm trying to structure a Mandelbrot set / Burning Ship plotter as if it were a game, with up to four pixels being calculated "simultaneously," plus a Matrix-like "digital rain" effect that also needs to be animated at least somewhat smoothly, thus the "Decay" array stuff, just to get more used to how games are structured, and next I'll try adding P/M graphics so the "player" can select a portion to zoom in on or out with a joystick or paddles. It already uses the keyboard to do this kind of awkwardly, and when I remembered the 400/800 didn't have a built-in numeric keypad, I rationalized that maybe there's a CX85 attached, with its handler taking up some memory so maybe I shouldn't go grabbing it all for myself 😅 Quote Link to comment Share on other sites More sharing options...
+yetanothertroll Posted June 10 Author Share Posted June 10 Are comparisons with multiples of 256 optimized in certain cases? For example, (x < 512) doesn't even need to look at x's low order byte, same for (768 <= x). Could it be worth doing if it isn't being done already? Quote Link to comment Share on other sites More sharing options...
+yetanothertroll Posted June 12 Author Share Posted June 12 Wow, string variables are pure evil, always 256 bytes even when they might never actually hold more than 20 characters or so. So much for KISS when it comes to a kerning table for a proportional font to make every column count in graphics modes 11 and 9 😿 Quote Link to comment Share on other sites More sharing options...
TGB1718 Posted June 12 Share Posted June 12 1 hour ago, yetanothertroll said: Wow, string variables are pure evil, always 256 bytes even when they might never actually hold more than 20 characters or so. It's probably the worst thing about FB, strings are so limited if you need something bigger and so wasteful if they are small. 1 Quote Link to comment Share on other sites More sharing options...
dmsc Posted June 13 Share Posted June 13 (edited) Hi! 6 hours ago, yetanothertroll said: Wow, string variables are pure evil, always 256 bytes even when they might never actually hold more than 20 characters or so. So much for KISS when it comes to a kerning table for a proportional font to make every column count in graphics modes 11 and 9 😿 4 hours ago, TGB1718 said: It's probably the worst thing about FB, strings are so limited if you need something bigger and so wasteful if they are small. Yes. For most uses - like the above, a string variable is not the proper way to store data, you need to use byte arrays (for data that can change) or byte data (for data that is predefined). You can even store strings in a byte data, this example: DATA str1() BYTE = "My short text" DATA str2() BYTE = "My LONG text" PROC Show x ? "The String: '"; $(x); "' is "; $(x)[4,5] ENDPROC @Show &str1 @Show &str2 Prints: The String: 'My short text' is short The String: 'My LONG text' is LONG For a table, just a byte data is better: DATA table() BYTE = 1, 2, 3, 5, 7, 11, 13, 17, 19 sum = 0 FOR i=0 TO 8 ? table(i), sum = sum + table(i) NEXT i ? : ? "Sum: ", sum IMHO, using those two, the limit of 256 character strings is not that relevant. Use string only for small texts and printing. Have Fun! Edited June 13 by dmsc 1 1 Quote Link to comment Share on other sites More sharing options...
+yetanothertroll Posted June 13 Author Share Posted June 13 (edited) Nice! However, one should be careful with variable names starting with Inc, Dec, and possibly other keywords: 000000r 1 ; Variables 000000r 1 .segment "HEAP" 000000r 1 .export fb_var_REDIBLE 000000r 1 xx xx xx xx fb_var_REDIBLE: .res 6 ; Float variable 000004r 1 xx xx 000006r 1 .export fb_var_INCREDIBLE 000006r 1 xx xx xx xx fb_var_INCREDIBLE: .res 6 ; Float variable 00000Ar 1 xx xx 00000Cr 1 .export fb_var_REDULOUS 00000Cr 1 xx xx fb_var_REDULOUS: .res 2 ; Word variable 00000Er 1 .export fb_var_INCREDULOUS 00000Er 1 xx xx fb_var_INCREDULOUS: .res 2 ; Word variable 000010r 1 .export fb_var_LAREIMUST 000010r 1 xx xx fb_var_LAREIMUST: .res 2 ; Word variable 000012r 1 .export fb_var_DECLAREIMUST 000012r 1 xx xx fb_var_DECLAREIMUST: .res 2 ; Word variable 000014r 1 .export fb_var_LAREIDO 000014r 1 xx xx xx xx fb_var_LAREIDO: .res 6 ; Float variable 000018r 1 xx xx 00001Ar 1 .export fb_var_DECLAREIDO 00001Ar 1 xx xx xx xx fb_var_DECLAREIDO: .res 6 ; Float variable 00001Er 1 xx xx Even array variable names might have shadows tagging along, 0000B4r 1 .export fb_var_DECAYPIXELX 0000B4r 1 xx xx fb_var_DECAYPIXELX: .res 2 ; Byte Array variable 0000B6r 1 .export fb_var_DECAYPIXELY 0000B6r 1 xx xx fb_var_DECAYPIXELY: .res 2 ; Byte Array variable 0000B8r 1 .export fb_var_DECAYTARGET 0000B8r 1 xx xx fb_var_DECAYTARGET: .res 2 ; Byte Array variable 0000BAr 1 .export fb_var_DECAYCURRENT 0000BAr 1 xx xx fb_var_DECAYCURRENT: .res 2 ; Byte Array variable ... 0000D8r 1 .export fb_var_AYPIXELX 0000D8r 1 xx xx fb_var_AYPIXELX: .res 2 ; Word variable 0000DAr 1 .export fb_var_AYPIXELY 0000DAr 1 xx xx fb_var_AYPIXELY: .res 2 ; Word variable 0000DCr 1 .export fb_var_AYCURRENT 0000DCr 1 xx xx fb_var_AYCURRENT: .res 2 ; Word variable 0000DEr 1 .export fb_var_AYTARGET 0000DEr 1 xx xx fb_var_AYTARGET: .res 2 ; Word variable Edited June 13 by yetanothertroll Interesting quarry of a typo'ed variable hunt Quote Link to comment Share on other sites More sharing options...
dmsc Posted June 13 Share Posted June 13 Hi! 13 hours ago, yetanothertroll said: Nice! However, one should be careful with variable names starting with Inc, Dec, and possibly other keywords: Yes, this is an unfortunate inheritance from Atari BASIC (and most 8-bit BASICs): you can't begin a variable name with a statement. See this in Atari BASIC: The "PRINTED=0" one shows "-1" in the C64 and gives a confusing error in the BBC; the "NOTA=2" gives an error in the C64 and the BBC - probably better than simply showing the odd result as in the Atari. The parser in FastBasic is a little more forgiving, but there are a lot of possible keywords that can clash with variable names. If you are using the PC compiler, you can use the '-l' to output the parsed program and see if any variable got mangled. I could add an option to require a space after any keyword, this would make all of those parsing gotchas go away. Have Fun! Quote Link to comment Share on other sites More sharing options...
_The Doctor__ Posted June 13 Share Posted June 13 um LET COMMON=3 Quote Link to comment Share on other sites More sharing options...
+yetanothertroll Posted June 13 Author Share Posted June 13 After I had my filthy filthy way with your pmtest.bas, compiling with -l gives me no apparent variable name mangling, ' P/M test program, cribbed from dmsc's pmtest.bas GRAPHICS 0 ' Setups graphics mode PRINT FRE(), PMGRAPHICS 2 ' And P/M mode PRINT FRE() ' How to implement the fifth player thing? FOR I = 0 TO 3 MSET PMADR(I), 128, 0 ' Clears P/M 0-3 Memory NEXT I PAUSE FOR I = -4 TO -1 J = 16 + I * 4 + RAND(4) PRINT I, J SETCOLOR I, J, 10 NEXT I ' P/M data and blank (to clear P/M) DATA PMDATA() PMDATA BYTE ROM = $38, $44, $54, $44, $38 ' Initial Conditions DIM XPOS(3), YPOS(3), YOLD(3), XSPD(3), YSPD(3) DIM DECAY(3) BYTE, PITCH(3) BYTE FOR I = 0 TO 3 XPOS(I) = 6400 YPOS(I) = 2560 YOLD(I) = 0 XSPD(I) = 64 - I * 16 YSPD(I) = - I * 64 DECAY(I) = 0 PITCH(I) = 0 NEXT I REPEAT I = (I + 1) & 3 XPOS(I) = XPOS(I) + XSPD(I) YPOS(I) = YPOS(I) + YSPD(I) YSPD(I) = YSPD(I) + 2 ' Can the collision logic be used to skip all these if/elifs ' if sprite i isn't touching the screen border? ' Can some comparisons against multiples of 256 be optimized? ' eg, (x < 512) doesn't even need to look at x's low order byte ' ditto for (768 <= x) ' Is it worth sweating? IF(YSPD(I) < 0) AND(YPOS(I) < $0800) YSPD(I) = RAND(20) PRINT I ; " top dy=" ; YSPD(I) @ BOING I ELIF(0 <= YSPD(I)) AND($3600 <= YPOS(I)) YSPD(I) = - YSPD(I) XSPD(I) = XSPD(I) + (RAND(64) - 32) PRINT I ; " bottom dx=" ; XSPD(I) ; " dy=" ; YSPD(I) @ BOING I ENDIF IF(0 <= XSPD(I)) AND($6500 <= XPOS(I)) XSPD(I) = - XSPD(I) - 1 PRINT I ; " right dx=" ; XSPD(I) @ BOING I ELIF(XSPD(I) <= 0) AND(XPOS(I) < $1800) XSPD(I) = - XSPD(I) + 1 PRINT I ; " left dx=" ; XSPD(I) @ BOING I ENDIF @ MOVEPM I ' Move P/M Graphics ' Freaking P/M magnets/collisions, how do they work? ' if incollision(i) then xSpd(i) = -xSpd(i) K = DECAY(I) IF 0 = K SOUND I ELSE SOUND I, PITCH(I), 8, K DECAY(I) = K - 1 ENDIF UNTIL KEY() WHILE KEY() GET K WEND SOUND PRINT FRE(), PMGRAPHICS 0 PRINT FRE() PRINT PRINT "OK" GET K END PROC MOVEPM MOVEPM MPI X = XPOS(MPI) / 128 Y = PMADR(MPI) + YPOS(MPI) / 128 YO = YOLD(MPI) A = ADR(PMDATA) PAUSE IF Y <> YO MSET YO, 5, 0 ' Clear old sprite MOVE A, Y, 5 ' Draw sprite at new vertical pos. ENDIF PMHPOS MPI, X ' Set new horizontal position YOLD(MPI) = Y ENDPROC PROC BOING BOING B DECAY(B) = 8 PITCH(B) = 64 + 48 * B + (RAND(64) - 32) SETCOLOR B - 4, RAND(16), RAND(16) ENDPROC ...although the repeated proc names are odd enough, but no problemo, but as for the vars, 00000Er 1 .export fb_var_DECAY 00000Er 1 xx xx fb_var_DECAY: .res 2 ; Byte Array variable 000010r 1 .export fb_var_PITCH 000010r 1 xx xx fb_var_PITCH: .res 2 ; Byte Array variable 000012r 1 .export fb_var_AY 000012r 1 xx xx fb_var_AY: .res 2 ; Word variable What's this "AY" variable? Where did it come from? So far as I can tell, the compiled program works just fine, but it just has this "AY" variable that isn't used anywhere. Maybe I should see if it causes a conflict if there's also an actual AY or AY% variable in the source code Quote Link to comment Share on other sites More sharing options...
+yetanothertroll Posted June 13 Author Share Posted June 13 28 minutes ago, _The Doctor__ said: um LET COMMON=3 OTOH, Quote Link to comment Share on other sites More sharing options...
+yetanothertroll Posted June 13 Author Share Posted June 13 19 hours ago, dmsc said: ... IMHO, using those two, the limit of 256 character strings is not that relevant. Use string only for small texts and printing. Have Fun! 256 bytes for a temp string that will have only two characters ever is not fun, but is mutable data kosher? Data LTKern() Byte ROM = "1-1+141719.4.7.9.+(-(+7.#7-)-1-7+)+1+7+." Data LTKtmp() Byte = 2, 0, 0 ' Caution, mutable! aLT = <$ aLTKern = <Kern aLTKtmp = <Ktmp ... ' Search kerning table LTKern() for c-p digraph If i < k LTKtmp(1) = c LTKtmp(2) = p j = LTKern(0) - 1 ' Len($(aLTKern)) - 1 Do If $(aLTKtmp) = $(aLTKern)[j,2] ' Found, keep them close together Exit ElIf j = 1 ' Not found, separate them Plot X, LTY + 5 Dec X Exit EndIf j = j - 2 Loop EndIf Quote Link to comment Share on other sites More sharing options...
dmsc Posted June 13 Share Posted June 13 Hi! 1 hour ago, yetanothertroll said: ...although the repeated proc names are odd enough, That bug was fixed in current version..... I should prepare a new release sometime... not enough time 1 hour ago, yetanothertroll said: but no problemo, but as for the vars, 00000Er 1 .export fb_var_DECAY 00000Er 1 xx xx fb_var_DECAY: .res 2 ; Byte Array variable 000010r 1 .export fb_var_PITCH 000010r 1 xx xx fb_var_PITCH: .res 2 ; Byte Array variable 000012r 1 .export fb_var_AY 000012r 1 xx xx fb_var_AY: .res 2 ; Word variable What's this "AY" variable? Where did it come from? So far as I can tell, the compiled program works just fine, but it just has this "AY" variable that isn't used anywhere. Maybe I should see if it causes a conflict if there's also an actual AY or AY% variable in the source code It came from your "DECAY(I)=0", this is first parsed as "DEC AY", creating a variable "AY", then the parser sees that the line is not completely parsed and rewinds, doing the correct parse. The variable is not cleared, as the parser currently does not remove created variables. This could be fixed in the PC compiler, but it is not really fixable in the Atari IDE. Have Fun! 1 Quote Link to comment Share on other sites More sharing options...
dmsc Posted June 13 Share Posted June 13 Hi! 33 minutes ago, yetanothertroll said: 256 bytes for a temp string that will have only two characters ever is not fun, but is mutable data kosher? Data LTKern() Byte ROM = "1-1+141719.4.7.9.+(-(+7.#7-)-1-7+)+1+7+." Data LTKtmp() Byte = 2, 0, 0 ' Caution, mutable! aLT = <$ aLTKern = <Kern aLTKtmp = <Ktmp ... ' Search kerning table LTKern() for c-p digraph If i < k LTKtmp(1) = c LTKtmp(2) = p j = LTKern(0) - 1 ' Len($(aLTKern)) - 1 Do If $(aLTKtmp) = $(aLTKern)[j,2] ' Found, keep them close together Exit ElIf j = 1 ' Not found, separate them Plot X, LTY + 5 Dec X Exit EndIf j = j - 2 Loop EndIf Doing this with strings is bigger and slower than using the byte array directly: Data LTKern() Byte ROM = "1-1+141719.4.7.9.+(-(+7.#7-)-1-7+)+1+7+." aLT = <$ ... ' Search kerning table LTKern() for c-p digraph If i < k For j = LTKern(0)-1 TO 1 Step -2 If LTKern(j) = c AND LTKern(j+1) = p Then Exit ' Found, keep them close together Next If J=-1 ' Not found, separate them Plot X, LTY + 5 Dec X EndIf EndIf In fact, with a double IF it would be faster, as the second array access won't happen if not needed: Data LTKern() Byte ROM = "1-1+141719.4.7.9.+(-(+7.#7-)-1-7+)+1+7+." aLT = <$ ... ' Search kerning table LTKern() for c-p digraph If i < k For j = LTKern(0)-1 TO 1 Step -2 If LTKern(j) = c If LTKern(j+1) = p Then Exit ' Found, keep them close together Endif Next If J=-1 ' Not found, separate them Plot X, LTY + 5 Dec X EndIf EndIf Note that in FastBasic, taking the address of an array/data/string and storing it in a variable is slower than using it directly. "&STR$" (or "ADR(STR$)") is compiled to just loading the address from the var, just one token. Have Fun! 1 Quote Link to comment Share on other sites More sharing options...
_The Doctor__ Posted June 13 Share Posted June 13 Let works for commands, it does not work for operators. 10 LET GOTO=2 20 PRINT GOTO 1 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.