Jump to content
senior_falcon

XB Game Developers Package

Recommended Posts

On 2/22/2021 at 9:55 AM, senior_falcon said:

I have made some progress in adding assembly support to compiled code. So far a compiled program can CALL LINK to an assembly support routine and return to the compiled code. The two test A/L subs are simple; PRINTA fills the screen with "A" and PRINTB fills the middle of the screen with "B"

As yet no variables can be passed in either direction with NUMREF, NUMASG, STRREF, STRASG. I have hopes these can be modified so this possible.

More progress: I can now NUMREF and NUMASG between a compiled program and assembly support routines. The assembly test program fetches a numeric variable from the compiled program with NUMREF, squares it, and and sends it back to the compiled program with NUMASG.

Next up is STRREF which should be easy, then STRASG which will probably take more thought.

 

(Edit) As expected, STRREF was straightforward. Now for STRASG.

Edited by senior_falcon
  • Like 7

Share this post


Link to post
Share on other sites

Now NUMREF, NUMASG, STRREF, and STRASG are able to pass values and strings back and forth between a compiled program and assembly language support routines.

I was a little surprised when STRASG worked the first time! STRASG only requires 7 lines of code.

This is a work in progress and there will be some nuances to work out.

Of course, the gold standard is this: will it work with THE MISSING LINK? That has yet to be attempted.

 

(Edit) CSN and CNS still need to be worked out.

Edited by senior_falcon
  • Like 5

Share this post


Link to post
Share on other sites
Posted (edited)

I am super excited. I just ran a compiled version of T40DEMO which (almost) worked fine. It bypassed certain parts of the demo.I suspect that is because when I pressed "enter" it bypassed some of the steps before I got my finger off the key. (EDIT- I take it back. It did not bypass anything.)

The beauty of this approach is that it should be able to work with any assembly subs you want to use

 

Edited by senior_falcon
  • Like 6

Share this post


Link to post
Share on other sites

I have worked out a few minor issues that arise when running T40XB with the compiler. Now the T40DEMO program works perfectly...

Except that it is too fast now. When there are two CALL LINK("INPUT"...) statements in a row, it is hard to get off the enter key fast enough to avoid entering a null value for the second one. I will modify INPUT so that it will not start until no key is pressed. I will also add a DELAY to make it easier to port from XB to compiled XB. Using FOR/NEXT loops gives drastically different delays.

  • Like 8

Share this post


Link to post
Share on other sites
Posted (edited)

Here is a little teaser for you. The two animated GIFs below show the LINES program from TMLDEMO. One of them is running in Extended BASIC and the other is compiled. Can you figure out which is which? (Remember that you won't get a 20x increase in speed by compiling TML. It already spends much of its time doing the graphics routines which are already in assembly. But any code in XB is MUCH faster.)

 

LINES.gif

 

 

Edited by senior_falcon
  • Like 11

Share this post


Link to post
Share on other sites
2 hours ago, senior_falcon said:

LINES.gif

              1st=TML                                                                                                2nd=compiled

 

:grin:

  • Like 1

Share this post


Link to post
Share on other sites

Yep. The code to draw the line is exactly the same, but the XB code to determine where to draw the line and the color is compiled, so the end result looks like it is about 3x faster.

  • Like 2
  • Thanks 2

Share this post


Link to post
Share on other sites
6 hours ago, senior_falcon said:

Here is a little teaser for you. The two animated GIFs below show the LINES program from TMLDEMO. One of them is running in Extended BASIC and the other is compiled. Can you figure out which is which? (Remember that you won't get a 20x increase in speed by compiling TML. It already spends much of its time doing the graphics routines which are already in assembly. But any code in XB is MUCH faster.)

 

LINES.gif

 

 

Might be time to compile TMNT ;)

  • Like 2

Share this post


Link to post
Share on other sites
44 minutes ago, Tursi said:

Might be time to compile TMNT ;)

How much bigger is compiled BASIC than byte coded BASIC?

Share this post


Link to post
Share on other sites
3 hours ago, TheBF said:

How much bigger is compiled BASIC than byte coded BASIC?

The compiled code is usually around 2/3 the size of the source XB program. In addition to that there is about 6K or more of runtime routines, depending on how complex your program is.

So a 10 byte program will be around 6K long. A 9K program will be about 12K long  and an 18K program will be about 18K long when compiled.

This will vary depending on your programming style and how it happens to compile. If the compiled program turns out to be too big, there is the option of putting the runtime routines in low memory. But this only applies to straight XB. If you are using assembly support subroutines like The Missing Link, then this is not possible.

  • Thanks 1

Share this post


Link to post
Share on other sites
10 hours ago, senior_falcon said:

So a 10 byte program will be around 6K long. A 9K program will be about 12K long  and an 18K program will be about 18K long when compiled.

@senior_falcon Don't forget to mention that a program in excess of 24K will compile down to 24K, so it'll run on the system's upper-expansion memory even though it's larger than 24K, which is especially handy.

  • Like 3

Share this post


Link to post
Share on other sites
10 hours ago, senior_falcon said:

The compiled code is usually around 2/3 the size of the source XB program. In addition to that there is about 6K or more of runtime routines, depending on how complex your program is.

So a 10 byte program will be around 6K long. A 9K program will be about 12K long  and an 18K program will be about 18K long when compiled.

This will vary depending on your programming style and how it happens to compile. If the compiled program turns out to be too big, there is the option of putting the runtime routines in low memory. But this only applies to straight XB. If you are using assembly support subroutines like The Missing Link, then this is not possible.

Wow that's is amazing. Is the extra 1/3 in the BASIC program mostly the labels and comments that come along for the ride or is the byte code implementation not very efficient?

 

I have noticed this in my Forth compilers. The logic for building interpreters in the old days was to sacrifice speed for reduced code size. But the 9900 has so many higher level functions that you don't get this advantage until you make some significantly complex functions in your program and then re-use them a great deal.  At the low level functions the 9900 is almost (not quite) 1:1 with the Forth VM.

 

 

  • Like 1

Share this post


Link to post
Share on other sites

Making slow but steady progress. Here is a compiled version of the TML tutorial program POTATOHEAD.

POTATO.gif

I hope to be able to make some adjustments to The Missing Link for a little more speed on the assembly side.

The main thing now is to modify the loader so you can save in XB and EA5 format.

 

Disc access is possible in the assembly support routines. I can use the disk catalog in T40XB within a compiled program.

Edited by senior_falcon
  • Like 9

Share this post


Link to post
Share on other sites

I was just reading the manual for the BASIC compiler for the first time and I saw this:

 

"The compiler generates "threaded code" which needs its own interpreter (the runtime routines)...

 

This of course got my attention.  I kind of have a "Threaded code R Us" mentality. :) 

Is there any place where I could learn more about this?

Is it a register based VM? 

Is it a byte coded VM? 

And many more...

 

Share this post


Link to post
Share on other sites

Below is a simple XB program.


100 CALL CLEAR
110 A$="Hello World!"
120 FOR I=1 TO LEN(A$)
130 CALL HCHAR(10,I,ASC(SEG$(A$,I,1)))
140 NEXT I
150 GOTO 150

Compiling this program produces the code below

       DEF RUNEA,RUN,RUNV,CON
RUNEA  B @RUNEA5
FRSTLN
L100
       DATA CLEAR
L110
       DATA LET,SV1,SC1
L120
       DATA LEN,SV1,NT1
FOR1
       DATA FOR,NV1,NC1,NT1,ONE,0,0
L130
       DATA SEGS,SV1,NV1,NC1,ST1
       DATA ASC,ST1,NT1
       DATA HCHAR,NC2,NV1,NT1
L140
       DATA NEXT,FOR1+2
L150
       DATA GOTO,L150
LASTLN DATA STOP
OPTBAS DATA 0
NC0
ZERO   DATA 0
ONE    DATA 1
PI     DATA 3
RND    DATA 0
NC1    DATA 1
NC2    DATA 10
NV0
NV1    DATA 0 I
NT1    DATA 0 
SC0
SC1    DATA SC1+2 
       BYTE 12,72,101,108,108,111,32,87,111,114
       BYTE 108,100,33
       EVEN
SV0
SV1    DATA 0 A$
ST1    DATA 0 
SA0
NA0
FRSTDT
LASTDT
       EVEN
ENDCC
       COPY "DSK1.RUNTIME1.TXT"
       COPY "DSK1.RUNTIME2.TXT"
       COPY "DSK1.RUNTIME3.TXT"
       END

The heart of the runtime routines is the code below. When the program starts up, R13 points to FRSTLN.

At L100 the address of CLEAR is read and put into R12, then B *R12 goes to CLEAR. Since CLEAR uses no input, the screen is cleared, then B @RTN is performed. Now it goes to LET which knows to look for two addresses. In this case it gets SC1 (Hello World!) and plugs that into SV1 (A$)

And so on...

You can see there are two parts of a compiled program. The compiled code is nothing but labels. There are no assembly instructions. Then the runtime routines have to be able to use those labels to mimic what XB does.

 

RTN    LIMI 2            the main loop, all subs come back to here
    LIMI 0
    MOV *R13+,R12
    B *R12            r12 has code. i.e. GOTO, SPRITE, etc.

 

Edited by senior_falcon
  • Like 3
  • Thanks 2

Share this post


Link to post
Share on other sites
13 hours ago, senior_falcon said:

Below is a simple XB program.


100 CALL CLEAR
110 A$="Hello World!"
120 FOR I=1 TO LEN(A$)
130 CALL HCHAR(10,I,ASC(SEG$(A$,I,1)))
140 NEXT I
150 GOTO 150

Compiling this program produces the code below

       DEF RUNEA,RUN,RUNV,CON
RUNEA  B @RUNEA5
FRSTLN
L100
       DATA CLEAR
L110
       DATA LET,SV1,SC1
L120
       DATA LEN,SV1,NT1
FOR1
       DATA FOR,NV1,NC1,NT1,ONE,0,0
L130
       DATA SEGS,SV1,NV1,NC1,ST1
       DATA ASC,ST1,NT1
       DATA HCHAR,NC2,NV1,NT1
L140
       DATA NEXT,FOR1+2
L150
       DATA GOTO,L150
LASTLN DATA STOP
OPTBAS DATA 0
NC0
ZERO   DATA 0
ONE    DATA 1
PI     DATA 3
RND    DATA 0
NC1    DATA 1
NC2    DATA 10
NV0
NV1    DATA 0 I
NT1    DATA 0 
SC0
SC1    DATA SC1+2 
       BYTE 12,72,101,108,108,111,32,87,111,114
       BYTE 108,100,33
       EVEN
SV0
SV1    DATA 0 A$
ST1    DATA 0 
SA0
NA0
FRSTDT
LASTDT
       EVEN
ENDCC
       COPY "DSK1.RUNTIME1.TXT"
       COPY "DSK1.RUNTIME2.TXT"
       COPY "DSK1.RUNTIME3.TXT"
       END

The heart of the runtime routines is the code below. When the program starts up, R13 points to FRSTLN.

At L100 the address of CLEAR is read and put into R12, then B *R12 goes to CLEAR. Since CLEAR uses no input, the screen is cleared, then B @RTN is performed. Now it goes to LET which knows to look for two addresses. In this case it gets SC1 (Hello World!) and plugs that into SV1 (A$)

And so on...

You can see there are two parts of a compiled program. The compiled code is nothing but labels. There are no assembly instructions. Then the runtime routines have to be able to use those labels to mimic what XB does.

 

RTN    LIMI 2            the main loop, all subs come back to here
    LIMI 0
    MOV *R13+,R12
    B *R12            r12 has code. i.e. GOTO, SPRITE, etc.

 

:) 

Well that looks pretty familiar!

 

The RTN routine is the inner interpreter of direct threaded Forth traditionally called NEXT.

Here's mine in RPN Assembler. Registers are renamed.  IP is R9  W (working register) is R8

l: _next     *IP+ W MOV,
                 *W B,

And so if I get this it is a token threaded system where each BASIC keyword is assigned a integer value with address threading (labels) for the user's code.

Clever combination.

 

Forth systems like Open Firmware (Sparc, Power Mac) uses a token coded system. I think it uses bytes to save space for the primary keywords with multiple tables.

 

I really appreciate the explanation. Thank you.

 

  • Like 3

Share this post


Link to post
Share on other sites

Hi,

 

I stumbled across an issue with Isabella 7. I develop a game using XB256 and it progresses well. Most testing is done in Classic99 with CPU Overdrive, but I compile once in a while. Suddenly last night my program went bezerk. Still working fine when interpreted, but showing strange behaviour after being compiled. The lines 

180 RC=0 :: CALL JOYST(1,X,Y) :: CALL KEY(1,K,S) :: X1=X1+X/4 :: CALL POSITION(#10,Q1,W1)
190 CALL LOCATE(#11,Q1,W1-16)
200 IF W1<32 THEN X1=MAX(X1,0) ELSE IF W1>210 THEN X1=MIN(X1,0)

should obviously attach sprite #11 on the left side of #10 and do so in XP. After compiling, it is about 4 chars to the left. Furthermore the joystick is only working sideways, no up or down.

I assume something in the (xb256?) runtime does not work anymore.

The program has 150 lines, the compiler produces a 33k Text-file and the OBJ has 51kb. Did I hit a size limit I am not aware of? The linker tells me "Runtime in high memory, 10216 bytes remaining". It is not my largest program, but the first using XB256.

 

Any help is appreciated!

 

Steve

 

Share this post


Link to post
Share on other sites

Here is a short program I wrote to test the joystick and sprite placement problems you report:


30 CALL CHAR(65,RPT$("F",32))
40 CALL MAGNIFY(2)
50 CALL SPRITE(#10,65,16,100,116,#11,66,14,100,100)
110 CALL POSITION(#10,R,C)
120 CALL JOYST(1,X,Y)
130 C=C+X/4 :: R=R-Y/4
140 CALL LOCATE(#10,R,C,#11,R,C-16)
145 CALL LINK("DELAY",100)
150 GOTO 110

This uses the joystick to move the 2 sprites around. They move the same in XB256 and when compiled. This shows the sprite placement and joystick response works as expected. Try this in XB256 to see how it works, then compile and see if it works the same.

 

I will check to see if your line 200 might behave differently. Getting IF/THEN/ELSE to work like XB was a challenge and maybe there is an error in that code.

Edited by senior_falcon
  • Like 2

Share this post


Link to post
Share on other sites

i just ran this program to test your line 200 and it too works the same in XB or when compiled

190 FOR W1=30 TO 230 STEP 100
191 FOR X=-5 TO 5 STEP 5
192 X1=X
195 PRINT "W1 X1=";W1;X1;
200 IF W1<32 THEN X1=MAX(X1,0)ELSE IF W1>210 THEN X1=MIN(X1,0)
205 PRINT "NEW X1=";X1
210 NEXT X :: NEXT W1

So everything in your post above seems to work properly for me.

 

Share this post


Link to post
Share on other sites
5 hours ago, SteveB said:

Hi,

 

I stumbled across an issue with Isabella 7. I develop a game using XB256 and it progresses well. Most testing is done in Classic99 with CPU Overdrive, but I compile once in a while. Suddenly last night my program went bezerk. Still working fine when interpreted, but showing strange behaviour after being compiled. The lines 

180 RC=0 :: CALL JOYST(1,X,Y) :: CALL KEY(1,K,S) :: X1=X1+X/4 :: CALL POSITION(#10,Q1,W1)
190 CALL LOCATE(#11,Q1,W1-16)
200 IF W1<32 THEN X1=MAX(X1,0) ELSE IF W1>210 THEN X1=MIN(X1,0)

should obviously attach sprite #11 on the left side of #10 and do so in XP. After compiling, it is about 4 chars to the left. Furthermore the joystick is only working sideways, no up or down.

I assume something in the (xb256?) runtime does not work anymore.

The program has 150 lines, the compiler produces a 33k Text-file and the OBJ has 51kb. Did I hit a size limit I am not aware of? The linker tells me "Runtime in high memory, 10216 bytes remaining". It is not my largest program, but the first using XB256.

 

Any help is appreciated!

 

Steve

 

Hi,

 

i had a similar problem in my Mega Menu program development.

The issue in my program was the array in line 200. I changed it to assign the value of max(x1,0) to a new variable and used the new variable in the statement e.g.

195 xn=max(x1,0)

200 if w1<32 then x1=xn

 

this solved my problem

 

 

  • Like 1

Share this post


Link to post
Share on other sites

I have had some odd behavior also with data statements.

Where the values become negative. 

 

I use Magellan to create a lot of screens and it uses read/data for char and x,y pos.

 

From time to time memory must get a little corrupted because I will get and error on the read.  and it will have the x,y or char value as a negative value!

I have not said anything for 2 reasons.  First, I assume I am doing something somewhere that is causing the problem.  VERY LIKELY!

Second I just toss in a ABS on the value that is failing and it works!   Though it is very strange! something like CALL HCHAR(X,Y,ABS(CHR))

 

On initial load of the program and run, it works fine.  But as I play test and make a changes, the consecutive executions show this behavior.

 

I do a load of strange things as I cobble together an attempt at a game.  So, I am certain I am doing something!  I do use arrays often.  I try to keep them under (10,10)

AH..  I may have just solved it.  I keep them under 10,10 so I can skip something... drawing a blank!  When I am less tired, I will remember.  But I am sure it is initialization related.

 

  • Like 1

Share this post


Link to post
Share on other sites
On 3/13/2021 at 8:12 PM, senior_falcon said:

 

RTN    LIMI 2            the main loop, all subs come back to here
    LIMI 0
    MOV *R13+,R12
    B *R12            r12 has code. i.e. GOTO, SPRITE, etc.

 

 

It goes without saying that the BASIC compiler has opened up a world of possibilities for BASIC programmers on the TI-99. How many years did we all toil to get some speed out of our favourite machine.

It was so painful to me it forced me to learn Forth. :) 

 

Now that I understand that this BASIC compiler is running direct threaded code I have some thoughts that might improve performance by as much as 20%.

I was surprised some years back when we compared a sieve benchmark between compiled BASIC and my indirect-threaded Forth. The Forth program ran quicker. 

That didn't make sense to me at the time because I believed the BASIC compiler was generating native code.

I now suspect that one of the reasons was because the RTN code above has four instructions whereas mine as an indirect threaded system has three instructions.

 

The RTN routine above is consuming a large percentage of the runtime in threaded systems.  It could approach 50% of the instructions being run for simple keywords!

In an equivalent Forth system every attempt is made to make this code as fast as possible.

The addition of LIMI 2 and LIMI 0 at 32 cycles essentially doubles the time taken by RTN.  The performance hit is not because the interrupts are running. They only run every 16mS.

 

Putting the interrupt polling in RTN is the simplest way to let the interrupts run but it is the worst thing to do for overall performance.

My experience with cooperative multi-tasking may provide an alternative.

 

If a routine called YIELD was created:

YIELD	LIMI 2
	LIMI 0
	RTN

This routine is placed in strategic places in the system code:

  1. At the beginning or end of every BASIC keyword that has internal loops. (VCHAR,HCHAR)
  2. At the beginning of a FOR/NEXT block
  3. At beginning of ALL I/O routines
     

That should give interrupts enough time to run in the course of any code although more tuning of the placement might be required.

 

Needless to say RTN should run in PAD RAM. I suspect it already does.

 

Something to put on the development stack perhaps, although I am sure there is no shortage of things that have higher priority.

  • Like 1

Share this post


Link to post
Share on other sites
11 hours ago, 1980gamer said:

I have had some odd behavior also with data statements.

Where the values become negative. 

 

I use Magellan to create a lot of screens and it uses read/data for char and x,y pos.

 

From time to time memory must get a little corrupted because I will get and error on the read.  and it will have the x,y or char value as a negative value!

I have not said anything for 2 reasons.  First, I assume I am doing something somewhere that is causing the problem.  VERY LIKELY!

Second I just toss in a ABS on the value that is failing and it works!   Though it is very strange! something like CALL HCHAR(X,Y,ABS(CHR))

 

On initial load of the program and run, it works fine.  But as I play test and make a changes, the consecutive executions show this behavior.

 

I do a load of strange things as I cobble together an attempt at a game.  So, I am certain I am doing something!  I do use arrays often.  I try to keep them under (10,10)

AH..  I may have just solved it.  I keep them under 10,10 so I can skip something... drawing a blank!  When I am less tired, I will remember.  But I am sure it is initialization related.

 

The only reason I can think of for odd behavior that takes a while to develop would be an error in the garbage collection for strings. But that doesn't seem likely because the program code would get corrupted before any variables.

If I understand you right, you are reading a number from a data statement that is positive in the data statement, and it reads the proper number except it is now negative?

I keep them under 10,10 so I can skip something... drawing a blank! 

By doing this you do not have to DIM the array. The drawback is that a lot of memory can be used needlessly. A 10x10 array is 800 bytes in XB  (actually 968 with option base 0). Divide that by 4 for compiled code. So you can see that if you need a 5x5 array, then the few bytes spend on DIM will save quite a bit of memory in the long run.

Edited by senior_falcon

Share this post


Link to post
Share on other sites

"If I understand you right, you are reading a number from a data statement that is positive in the data statement, and it reads the proper number except it is now negative?"

Yes, exactly.

 

Yes, DIM.  Not that I was considering space (memory)

I just might do this for less variable effort!  x(y)  vs.  X1 and X2 ... being lazy.  Or as I argue when I am called out... Working smarter not harder!  LOL

 

10 for y=0 to 9

20 x(y)= some random number between 1 and 9 or something.

30 next y

 

can't imagine doing 10 lines of X1=INT(RND... )   X2=INT...

 

plus I might add a line like: 25 if y>0 and x(y)=x(y-1) then 20  so I don't get 2 consecutive variable with the same value or some other crazy thing that pops into my head!

 

If this was for 28 sprites I would DIM it.  I just never bothered using the DIM if I needed less than 10.

My thinking LATE last night, was by not using DIM, I might have been messing with the compiler.  Not giving it what it needs to parse properly.

But I am getting this problem before I have compiled.

 

 

 

Share this post


Link to post
Share on other sites
2 hours ago, 1980gamer said:

But I am getting this problem before I have compiled.

I'm confused. Is the XB program reading the number negative instead of positive? Or does this only happen in a compiled program?

 

I should add that it takes longer to retrieve a number that is in an array. If you use a lot of arrays then by using simple constants and variables you would probably notice an improvement in speed.

Edited by senior_falcon

Share this post


Link to post
Share on other sites

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...