--------------------------------------------------------------------- Psion Series 3/3a OPL Programmers FAQ V1.0 April 1994 --------------------------------------------------------------------- This (unofficial) FAQ is maintained by Andrew Baldwin, ar.baldwin@ic.ac.uk or andrew@zarquon.demon.co.uk If you have any complaints, problems or suggestions for improvement, then please tell me! I will attempt to add to it any questions/solutions that come up on the comp.sys.psion newsgroup. If you have any OPL questions, that is the best place (on the Internet) to direct them to. ---------------------- Contents -------- 1. Introduction 1.1 What is OPL? 1.2 Starting out with OPL 1.3 OPL Vs. Other Languages 2. OPO's Vs. OPA's 2.1 OPO's 2.2 OPA's 3. Speed 3.1 Translated NOT Compiled! 3.2 How do I speed up my OPL programs? 4. Series 3/3a file formats 4.1 Graphics - .PIC files 4.2 (Series 3a) Sound - .WVE files 5. Revtran 6. Flickering Graphics - gUPDATE 7. OPL Questions & Answers 7.1 Arrays 7.2 Dialogs and Menus 7.3 Passing data from procedures 7.4 Returning owner info from OPL 7.5 Auto-off 7.6 Sprites 7.7 OS functions 7.8 Including Bitmaps in OPL files 7.9 FINDFIELD documentation error ---------------------- 1. Introduction =============== 1.1 What is OPL? ---------------- OPL is a language, similar in ways to BASIC, that has evolved through a range of Psion machines into its current incarnations on the Series 3 and Series 3a machines. It has been designed to produce small output code and be fast in operation. 1.2 Starting out with OPL ------------------------- The best way to start learning OPL is to work through the Programming Manual that comes with your machine. If you have had experience with other computer languages, you should have no trouble in learning OPL. 1.3 OPL Vs. Other Languages --------------------------- Currently, the only other ways to program the Series 3/3a machines are using the `C' language and Machine Code. To program in `C', you need to purchase the Psion `C' SDK directly from Psion. This is available in various versions costing up to about 300 UK pounds and gives you a PC `C' compiler, utility programs, and comprehensive (13 manuals) documentation. If you don't have access to a PC, or you don't want to spend the money on the SDK, the only other option currently open to you is to write some machine code. The processors in the Series 3 & 3a machines are NEC V30's which are Intel 8086 compatible, so if you are familiar with 8086 machine language or can get a book on it, this option is open to you. Giles Goddard (gsg@cix.compulink.co.uk) has written a program called S3asm which allows you to write machine code in 8086 assembly language. Hopefully this program will soon be available on the internet from src.doc.ic.ac.uk Although programming in machine code is much more complex than using OPL and can cause your machine to crash, it is quite safe to use on the Series 3/3a because of the machine's hardware memory protection. For an example of what is capable in assembly, take a look at the various 3d tank programs available by ftp on src.doc.ic.ac.uk (/packages/psion/icdoc/games). 2. OPO's Vs. OPA's ================== There are 2 different types of programs you can produce from OPL. 2.1 OPO's --------- OPO files are what you normally produce when you choose "Translate" from the OPL editor. They run either from the RUN option in the editor, or from underneath the RunOpl icon on the system screen. If you RUN them from the editor you have the advantage that if any errors are encountered, the editor will jump to the position in the OPL code where the error occurred, thus making programs much easier to debug. OPO programs do not need to be aware of system events and messages, since the system will take care of killing the program if this is requested from the system screen (by pressing delete when the bold text of a running program is highlighted). 2.2 OPA's --------- OPA files are produced when a header containing at least: APP ProgName TYPE 0 ENDA is added to the top of the program. The format of these headers and also some sample OPA's (applications) are given in the `Advanced Topics' chapter in the programming manuals. OPA's can have their own ICON and have to be INSTALLED on the system screen. Writing an OPA is no simple task. OPA's *MUST* be aware of and respond properly to system events and messages passed to them by the operating system. This means that the OPA should continually use either TESTEVENT or GETEVENT. If the OPA is waiting for user interaction, it should call GETEVENT. This will wait for an event (either a key-press or a system message) and ensure the program is not using up all the CPU time doing nothing (this would prevent the Auto-Off function from working). If the OPA is busy doing something, it should regularly call TESTEVENT. This will see if an event has occurred since the last check. If an event has occurred, the program should then call GETEVENT. At a minimum, OPA's *MUST* respond to the system message "X" and close down/quit when this is received. It *SHOULD* also respond to $402 (program has moved to background) by pausing and not taking up all the CPU time. If you write an OPA that uses any extra data files (either OPO modules, .PIC files or others), it is recommended that those go in a subdirectory with the same name as the program in the same APP directory as the program. Note: Something that is not pointed out in the manual is that when you tell an application what file it is to use as an icon, the icon file is actually included in the OPA file. This means it is not necessary to include the .PIC file along with any distributed applications - often seen and unnecessary! 3. Speed ======== 3.1 Translated NOT Compiled! ---------------------------- When you "Translate" an OPL program, it is not compiled in the same way that, for example, a C program would be. Instead it is converted into Q-Code, which is simply a more compact version of the original OPL code. This Q-Code is then interpreted whenever it is run. You may think that this results in a considerable speed loss when compared with programs written in `C', but this is only the case for very computationally intensive tasks. For graphical operations, there is very little difference since both C and OPL just use the relevant ROM calls to access the Window Server. 3.2 How do I speed up my OPL programs? -------------------------------------- To know how to speed up your OPL programs, you need to know what is slowing them down. The prime candidates are: i. Floating point maths ii. Procedures iii. Slow graphic commands i. There are 3 types of numerical variable in OPL, integers, long integers, and floating point variables. Integers are by far the fastest type to perform calculations with, so if you don't require either decimals, or integer numbers outside the range -32769 to 32767, you should ALWAYS use these. If you can't use integers, then you should use long integers, even for decimal calculations. Simply scale the integer up by a factor of 10 x decimal accuracy before calculation and then scale down again after. If this is not possible then you have to use floating point variables and your program will slow down appreciably. If you require Sin or Cos functions in your program, you should not use the provided functions since they are VERY slow. Instead, create an array early in the program that contains the values of Sin and Cos at required angles, and then look the values up in this array whenever required. Alternatively, you could output a file containing these values whenever the program is first run. ii. Whenever your program calls a procedure, that procedure is loaded into the Series 3/3a's memory and run. This loading operation has a time overhead. The Series 3a has a set of commands to handle procedure caching, and you should use these on the 3a if you have any time intensive loops that call other procedures very often. Given enough cache memory, these procedures are only loaded into memory once and the loop will run much faster. The Series 3 does not offer these commands, and you should instead try and move the code from the procedures "in-line", i.e. into the main body of the loop. iii.The graphics on the Series 3/3a are generally very fast, but the fastest graphics command in OPL is gCOPY. If you are performing any complex tasks with many moving windows, you may be able to produce a considerable speed increase by instead using only gCOPY's onto one displayed window. 4 Series 3/3a file formats ========================== 4.1 Graphics - .PIC files ------------------------- OPL supports only one type of graphics file format, .PIC files. These files can hold store multiple b/w bitmaps of any size. How do you create .PIC files? Since the gSAVEBIT command only saves .PIC files with only 1 image (or 2 on the Series 3a), you need some other method to produce multi-bitmap files. The simplest (free) way to produce .PIC files with more than one image is to use the program WSPCX.EXE (available by ftp from src.doc.ic.ac.uk) which runs on a PC. This program accepts the more common .PCX format and can convert these .PIC files and also take multiple single image .PIC files and produce one multi image .PIC file from them. 4.2 (Series 3a) Sound - .WVE files ---------------------------------- On the Series 3a, OPL offers the ability to play .WVE sample files. These can either be produced by the built in RECORD application or you can use the WAV2WVE.EXE program (also available by ftp from src.doc.ic.ac.uk) for the PC to covert samples from the common PC .WAV format to the Psion .WVE format 5. Revtran ========== As mentioned earlier, OPL programs are not compiled, simply translated into a more compact form. This means that it is possible to recover from an OPO or OPA file, an OPL file that is exactly the same in function as the original OPL code. There is a program available that allows you to do this, it is known as Revtran (available by ftp from src.doc.ic.ac.uk). This can be very useful for discovering how an author has achieved certain effects in a program, but code produced by this method should never be re-distributed. If an author has made it plain that they do not permit the use of Revtran on their software you should not do so. However, if you are an author who does not wish Revtran to be used on your software, there is a simple way to prevent the current version (2.4) of Revtran from being able to reverse translate a module. Since Revtran can only handle line lengths up to 255 characters, and also since it expands floating point number, if you insert a line such as: LOCAL dm$(100) dm$ = GEN$(0.0,1) + GEN$(0.0,1) + GEN$(0.0,1) + (etc....) with about 16 of the "GEN$(0.0,1)" commands, then Revtran will be unable to work on the translated module. Note: this is only valid up to the current version of Revtran. Future versions may well be able to get round this line. If you have nothing against people seeing your source code, I encourage you to distribute the original OPL source along with a program, which is much more useful than Revtran output anyway, and even more so if it is well documented. In the end this can only benefit the Psion community since software is the life blood of any machine. 6. Flickering Graphics - gUPDATE ================================ If a program you write uses graphic (g) commands to any extent whatsoever, you will benefit both in speed and look by judicious use of the gUPDATE command. Early in a program you should use the command gUPDATE OFF This disables automatic graphic updating and graphics will only now be drawn to the screen when either: * gUPDATE is called * The gUPDATE buffer becomes to full - this happens after between about 10 and 20 graphic operations depending on type. If you are performing graphic operations that take up a large part of the screen, the use of gUPDATE will not prevent graphics from flickering. The best way I have so far found of preventing this is the following. * Ensure you have a background window and a foreground window * make the foreground window active (gUSE) * make the foreground window non-visible (gVISIBLE OFF) * draw to the foreground window * make the foreground window visible (gVISIBLE ON) * call gUPDATE This method doesn't work if you have to many graphic operations since gUPDATE will trigger too soon and the foreground window will momentarily disappear. In this situation you will have to try some slower double buffering method where you again have 2 windows, but draw to the one in the background before bringing it to the foreground and calling gUPDATE. 7. OPL Questions & Answers ========================== 7.1 Arrays ---------- Simulating Multi Dimensional Arrays ----------------------------------- People frequently ask how to do multi-dimensional arrays in OPL, since it only supports 1-dimensional arrays normally. Say for example, you want a 2 dimensional integer array p%(x,y) where x and y are the maximum size of the dimensions. The simplest way to do this is to declare an array p%(x*y) although you will need to put the explicit result of x*y (e.g. x=10, y=10 -> p%(100) ) in since OPL will complain otherwise. To access the elements of the array you simply refer to p% ( (b%-1)*x% +a% ) where a%,b% is the element you want. Since you are performing an integer multiply every time you need to access the array, a slightly quicker method (although requiring more memory) suggested by Matt Millar (uv94002@black.ox.ac.uk) is to create an array of pre-calculated values of (b%-1)*x% and store these in another array of size y% and then calling p% ( m%(b%) + a% ) Dynamic Arrays -------------- OPL arrays are not dynamic, but must be declared at a fixed size at the beginning of a procedure. To get around this, you can utilize the memory allocation facilities offered directly on the 3a, and via OS calls on the Series 3. The commands ALLOC, FREEALLOC, etc. on the 3a are simply handles to OS functions which can be found in Clive Feather's excellent "Psionics" files (available by ftp from src.doc.ic.ac.uk). If you are using the heap allocator commands on a 3a, you should thoroughly read through the relevant Advanced Topics section in the Programming Manual (starts Page 145). The use of these commands can be simplified by creating a set of procedures to access your simulated array. 7.2 Dialogs and Menus --------------------- The Dialog and Menu commands of OPL offer the facility to create programs that look very similar to the built in applications of the machine, however, there are some limitations. Dialogs ------- Whenever you open a dialog box with the DIALOG command, your program completely loses control until the DIALOG is exited. This means it is impossible to have fields in dialogs change when information in different fields is altered. This is often seen in some of the built in apps, but is only achievable using the object orientated facilities offered by the `C' SDK. When using Dialogs in an OPA, you should always call LOCK ON before the DIALOG command, and LOCK OFF afterwards. This tells the system that your application is not receptive to system messages (such as "X" - quit). Menus ----- Similarly to dialogs, when a menu is displayed, control is lost by the program. You should again use LOCK ON before the menu and LOCK OFF afterwards in an OPA. 7.3 Passing data from procedures -------------------------------- Some people bemoan the fact that OPL procedures can only return single parameters with the RETURN command. The solution to this is of course use GLOBAL variables which should be declared in procedures called before the relevant procedure. Alternatively, you could pass an address to a procedure - the address being that of a variable or array where you want the data to be returned to. 7.4 Returning owner info from OPL --------------------------------- The machine owner information (name, address etc.) is stored as an environment variable. To return these 4 strings, the following procedure can be used: (A GLOBAL variable own$(4,50) is assumed to have been previously declared outside this procedure) PROC Getowner: LOCAL buf$(255) LOCAL nam$(6) LOCAL failed% LOCAL regs%(6) LOCAL t% nam$ = "$WS_PW" regs%(1) = $2100 regs%(4) = LEN(nam$) regs%(5) = ADDR(buf$)+1 regs%(6) = ADDR(nam$)+1 failed% = 1 AND OS($8B,ADDR(regs%())) IF failed% RETURN ELSE regs%(1) = regs%(1) AND $FF POKEB ADDR(buf$),regs%(1) t%=1 DO own$(t%) = MID$(buf$,PEEKB(ADDR(buf$)+(4*t%)+1)+1, (cont)PEEKB(ADDR(buf$)+(4*t%))) t% = t%+1 UNTIL t%=5 ENDIF ENDP 7.5 Auto-off ------------ The Series 3/3a machines employ a complex multitasking operating system. This means that it is possible for program to run in the background while the machine is doing other things. If you write a program that is designed to operate in the background and it runs continuously rather than waiting for an event, it is important that it doesn't prevent the machine's Auto-off function from working. To do this, two things must happen. * The program must pause for at least 2/30 second (pause 2) in its loop to give the Auto-off function time to work. * The program must be set GenMarkNonActive To set a program GenMarkNonActive you must CALL($138b) This can be cancelled (GenMarkActive?) CALL($128b) 7.6 Sprites ----------- The Series 3a offers limited support for *a* SPRITE with a set of commands. What is not made clear in the manual is that ONLY 1 sprite is available. If you require more than one sprite in your program, it is necessary to write your own "sprite" commands using gCOPY's and other commands. 7.7 OS functions ---------------- The commands OS and CALL give access to the built in operating system functions of the 3 and 3a. Unfortunately, very few of these are documented in the manuals. To find out the functions and syntax of many of these, you should obtain the "Psionics" files of Clive Feather (available by ftp from src.doc.ic.ac.uk) , which contain information about these and also many other topics of interest to Psion programmers. 7.8 Including Bitmaps in OPL files ---------------------------------- Something that is very common in applications is for the bitmaps used by a program to be stored as part of the bitmap file used for an applications icon. To gain access to a bitmap that is stored this way requires the following piece of code: PROC Cutpics%:(nim%) LOCAL id%,wi%,ht% LOCAL f$(128),buf%(100) LOCAL h%,l%,o% f$ = cmd$(1) l% = IOOPEN(h%,f$,$400) IF l% GOTO cl2 ENDIF l% = IOREAD(h%,ADDR(buf%(1)),200) IF l% <= 140 GOTO cl ENDIF IF buf%(2) <> $l+(%O*256) GOTO cl ENDIF o% = 23+(buf%(11) AND $ff) l% = PEEKW(ADDR(buf%(1))+o%) IF l% <> %P+(%I*256) GOTO cl ENDIF CALL($5f8d,1,o%,0,0,0) cl:: IOCLOSE(h%) cl2:: id% = gLOADBIT(f$,0,nim%) RETURN id% ENDP Call hte procedure with the image number for the file, and it will return the id%() number as if you had called gLOADBIT directly. 7.9 FINDFIELD documentation error --------------------------------- Apparently there is an error in the manual documentation for the FINDFIELD command. (according to Anthony Kucernak (ark1000@cus.cam.ac.uk)) The correct definition of the command is: FINDFIELD(a$,start%,no%,flags%) where a$, start%, and no% are as displayed in the manual but flags% are the sum of two digits: 0 ... case-independent search 16 ... case-dependent search and 0 ... search backwards from current record 1 ... forwards from current record 2 ... backwards from end of file 3 ... forwards from start of file --------------- Here endeth the FAQ! Comments made to above: ---------------------------------------------------------------- >3.2 How do I speed up my OPL programs? >-------------------------------------- >iii.The graphics on the Series 3/3a are generally very fast, > but the fastest graphics command in OPL is gCOPY. If you are > performing any complex tasks with many moving windows, you > may be able to produce a considerable speed increase by > instead using only gCOPY's onto one displayed window. Another popular way in the graphic/game worlds is double buffering. This is where you would display one bitmap whilst drawing in the other; once the drawing is complete you switch the buffers so that the one you were drawing on is now displayed and the other will be the one you will now draw into. This can be simply achieved by creating two windows using and using gORDER to move the windows between levels It works quite nicely if you have an array WIN%(2) pointing to the two bitmaps and a variable currwin% which you can use as follows: use WIN%(currwin%) // select current window to draw into ... draw ... gORDER(WIN%(currwin%),0) // displays it and hides the other for free currwin%=3-currwin% // switch to the next buffer for the next // drawing (can't tell I can program C++ // can you ;-)) Make sure the two bitmaps are defined to be the same size though! BTW, do the later versions of the 3a still have the '%=' bug where if you type those characters really quickly (as you would if you were programming) the machine would interpret it as the diamond key and switch you to outline mode. It's particularly annoying because you cannot program the diamond key functions like in the other programs. God! Don't they teach orthogonality anymore :-) >try some slower double buffering method where you again have >2 windows, but draw to the one in the background before bringing >it to the foreground and calling gUPDATE. Oops, got a little ahead of me there; but it's 4:30am. So tough:-) Also, it's not actually that slow. I manage to move 5 objects w/ no trouble. >7.6 Sprites >----------- >The Series 3a offers limited support for *a* SPRITE with a set >of commands. What is not made clear in the manual is that >ONLY 1 sprite is available. Yeah, that was so annoying when I found that out! Hope this doesn't mean that they're going to release another machine until I've made my money back on this one!