20 October 2013, 17:08 | #1 |
Glastonbridge Software
Join Date: Jan 2012
Location: Edinburgh/Scotland
Posts: 2,243
|
Neat coding style and good habits
Lately I've been doing a complete overhaul of my game's entire source code. What a job! I wish I'd known more about computer science when I started, it could have saved me so much work. It has really driven home to me the importance of coding style for code maintainability (as well as the importance of comments, especially in asm, and I never thought I'd need them ) and programming professionally in higher-level languages has got me into good habits, and thinking about structure. A bowl of spaghetti is not a structure!
So I wondered if anyone has any style tips or rules they like to follow when coding in Asm on the Amiga? Some rules I've started putting into practice so far include: Naming conventions for functions. Use underscore in labels to implement "namespaces" so you know immediately which of your include files your functions are in when you see a call. Stop using DC statements to create data zones inside the code (i.e. global variables). Use RS and allocate space for structures dynamically, or put things on the stack. Start labels with underscore to indicate private methods. Well they are not really private! But this is how Python does it too, so I heard, which was a nice coincidence. Dynamic destructors! I put a pointer to a destructor subroutine at the start of every allocated memory block, which my delete function calls before freeing the memory. Generally I try to think how I would structure my code if I were writing in C++. The last trick I pulled was to implement exception handling. I have a little "throw" routine that looks as follows: Code:
throw move.l (SP)+,A0 cmp.w #$4AFC,(A0) bne.s throw jmp 2(A0) I recently learned what the instructions LINK and UNLK are for. I never used a register for a stack frame before, and I'm still just accessing stack objects via SP. I don't have any address registers left for such a purpose, but C compilers use this and I can see how it could make things more convenient. Exception handling could be even neater but the above works for now. Does anybody have any favourite ways of doing things that make things easier or neater? I'd love to hear them! |
20 October 2013, 18:48 | #2 |
Computer Nerd
Join Date: Sep 2007
Location: Rotterdam/Netherlands
Age: 47
Posts: 3,764
|
When using structures, I write them like you would in higher level languages such as C#:
Code:
; ; structures ; rsreset list.firstNode rs.l 1 list.lastNode rs.l 1 struct.list rs.l 1 ; ; method names ; list.new list.free list.addNode list.insertNode list.deleteNode |
20 October 2013, 23:51 | #3 |
Glastonbridge Software
Join Date: Jan 2012
Location: Edinburgh/Scotland
Posts: 2,243
|
For some reason I never considered using dots in label names. Maybe because you can't actually do that in C, for obvious reasons. I use local labels but that seems "special".
A long time ago I controversially chose a tab size of 10. 8 seemed a bit cramped, but then I was in the habit of putting labels and code on the same line. |
21 October 2013, 03:36 | #4 |
Join Date: Jul 2008
Location: Sweden
Posts: 2,269
|
This is an interesting topic I think a lot about. When I came back to Amiga a few years ago I immediately saw how antiquated my Amiga programming habits were, and that I had been stuck in this 1989 year's lump-all-code-into-a-single-file way of doing things. Some good tips:
1. Use the best tools you can find Use a scriptable editor and proper command line tools such as vasm, basm, phxass or genam (Devpac), and a linker, f.ex vlink, phxlnk or blink. IDEs like Seka, ASM-One and Maxxon Assembler are great for learning and doing quick tests, but really bad for writing actual programs. They are single-file assemblers that try to be monolothic and offer the All-In-One Total Assembly Experience™, but instead end up coaxing you into bad practices by forcing you to structure your program as a single massive file, resulting in poor reusability, longer build-times, and eventually a completely unmanageable code base. 2. Automate the build procedure Use tools like GNU Make to automate, enhance and speed up the build procedure. This way you don't have to open your programs and drag, select and click your way through menus and buttons every time you've changed an image or sound file. Don't do repetitive manual work if you can automate it with a bit of code. Languages like Python and Lua are great for writing simple tools to convert files or generate data tables, and you can even run them on classic Amigas. Even AmigaDOS scripts can help a long way. 3. Make full use of the assembler The whole point of using an assembler is to make machine language programming tolerable. Make use of macros, auto-optimization and other convenient features. Don't use the assembler like a simple machine code monitor. Enhance the assembler and abstract messy syntaxes using macros: Code:
macro gfxcall xref _LVO\1 move.l _GfxBase, a6 jsr _LVO\1(a6) endm macro cwaitv dc.b \1, 1, $ff, 0 endm macro cmove dc.w \2, \1 endm Code:
macro if_equal bne .\@! endm macro else_if bra .\@? .\@@ endm macro end_if .\@@ endm Code:
; Crufty movea.l (data, pc), a1 lea.l (-400, a0), a0 bsr.b X cmpi.l #100, d1 bne.b .nope bsr.w func1 bra.b .ok .nope: bsr.w func2 .ok: Code:
; Clean move.l data, a1 sub #400, a0 bsr X cmp.l #100, d1 if_equal bsr func1 else_if bsr func2 end_if 4. Use meaningful symbolic names Use meaningful names for things like constants and offsets, instead of magic numbers that obfuscate the code. Code:
; Bad move.l 4, a6 move.l (156, a6), a6 ; what's here? jsr -270(a6) ; what? move.w #%1101001110010010, $dff09a ; what does this do? move.w #$c3f9, $dff096 ; launch missiles? dc.l $3201ff00 ; lots of digits dc.l $01800128 dc.l $fa01ff00 dc.l $01800000 dc.l $fffffffe Code:
; Good gfxcall WaitTOF ; call WaitTOF in graphics.library move.w #int_clr|int_en, intena ; disable interrupts move.w #dma_set|dma_en|dma_all, dmacon ; enable all DMA cwaitv 50 cmove $128, color00 ; easy to understand Copper program cwaitv 250 cmove $000, color00 cend |
21 October 2013, 10:57 | #5 |
Natteravn
Join Date: Nov 2009
Location: Herford / Germany
Posts: 2,500
|
I can only agree with most what was written here. In bigger assembly projects (let's say a game) it is mandatory to follow some coding style. Lots of comments in the source and structuring by blank lines and/or ";------" makes it more readable. Use symbols for everything, so you can change them easily in a single place. Also, like Leffmann indicated, I would spread the source over many small modules. For example one for the game engine, one for the display-setup and copper, one for trackdisk routines, one for sound effects, one for the protracker player, one for BOB-handling, one for scrolling, etc.
Even when you write absolute code you should use sections to keep code, data und uninitialized data together, which will give you some advantages. The code will usually stay small enough to allow 16-bit branches this way, and you could implement a small data section, which you access through a base register. Also Chip-RAM data can easily be separated from the rest of the program. When the resulting program is no AmigaDOS executable but has to be loaded to an abosolute address, use a linker script to define the location of each section in memory. An example of coding style from my current game project "Solid Gold". The beginning of the scroll.asm module: Code:
* * Written by Frank Wille in 2013. * * I, the copyright holder of this work, hereby release it into the * public domain. This applies worldwide. * * set_scroll_pos(d0.w=xpos, d1.w=ypos) * $scroll(a5=View) * d2.w=xpos, d3.w=ypos, d4.w=ypos%BMAPH = $copper_scroll(a5=View) * $moveView(d0.w=xpos, d1.w=ypos) * load_copperback(d0.w=fileID) * include "custom.i" include "display.i" include "scroll.i" include "view.i" include "map.i" include "macros.i" ; from loader.asm xref td_loadcr ; from main.asm xref ovflowerr xref loaderr ; from blit.asm xref YOffTab ; from tiles.asm xref RowOffTab xref Tiles ; from map.asm xref Map xref FgMap xref MapWidth xref MapHeight xref ScrWidth xref ScrHeight xref MapRowOffTab near a4 code ;--------------------------------------------------------------------------- xdef set_scroll_pos set_scroll_pos: ; Set the map position of the top-left display edge. ; Warning: Needs to be followed by copper_scroll() and draw_tiles(), ; which redraws the whole bitmap. scroll() is for small movements only! ; d0 = xpos.w ; d1 = ypos.w move.l d2,a0 moveq #0,d2 move.w d1,d2 divu #BMAPH,d2 swap d2 movem.w d0-d2,Xpos(a4) ; initialize Xpos, Ypos, Ymod move.l a0,d2 ; determine our position on the copper background move.l CopperbarTab(a4),a1 subq.l #4,a1 asr.w #1,d1 move.w d1,CbackPos(a4) .1: addq.l #4,a1 move.w (a1),d0 sub.w d0,d1 bpl .1 move.l a1,CbackPtr(a4) add.w d0,d1 move.w d1,CbackOffs(a4) rts ;--------------------------------------------------------------------------- xdef scroll scroll: ; Scroll the View to match the current MapX/MapY coordinates. ; a5 = View ; Registers, except a4 - a6, are not preserved! bsr copper_scroll ; Load/update View position movem.w Vxpos(a5),d0-d1 movem.w d2-d3,Vxpos(a5) bra blitter_scroll ;--------------------------------------------------------------------------- ; Horizontal scroll codes for BPLCON1, xpos 0..15 ScrollTab: dc.w $0000,$00ff,$00ee,$00dd,$00cc,$00bb,$00aa,$0099 dc.w $0088,$0077,$0066,$0055,$0044,$0033,$0022,$0011 ... Label will start with a lower case character when it is a function. Data labels will always start with an upper case character. Usually I try to follow the 68k C-ABI, which defines registers d0-d1 and a0-a1 as volatile. The rest must be saved and restored. Exceptions (for performance reasons ) have to be mentioned in the function header. Last edited by phx; 21 October 2013 at 11:34. Reason: Mention label naming convention. |
21 October 2013, 14:18 | #6 | |
Glastonbridge Software
Join Date: Jan 2012
Location: Edinburgh/Scotland
Posts: 2,243
|
Quote:
In fact in Mr Beanbag any code that is specific to an individual game level (for instance to generate the copper list) is also loaded in dynamically. Although I'm now trying to do that with whole game worlds, and I'm faced with the formidable task of working out how to do dynamic linking, since the world handler will need to call functions in the main executable! Level handlers already have access to a small jump table via an address register so they can draw bobs &c. but I need something more comprehensive so I can add new functionality easily without worrying about breaking anything. |
|
23 October 2013, 01:04 | #7 |
Join Date: Jul 2008
Location: Sweden
Posts: 2,269
|
What I meant to say was that if you can find an existing command line tool or write a bit of code yourself to f.ex convert IFF images to raw format, then that automatization is to prefer over manually clicking around in some GUI-based tool each and every time the graphics have changed.
Loading data from disk is a good idea, and you can even use an overlaid executable to put all those files in the executable, but not load them until you need to. Personally I would put any data that is required for the whole duration of the program directly into the executable anyway. |
23 October 2013, 10:44 | #8 | |
Natteravn
Join Date: Nov 2009
Location: Herford / Germany
Posts: 2,500
|
Quote:
For everything more complex, like using real dynamic linking, you would probably have to write a linker yourself, or modify an existing one. For Sqrxz and Solid Gold I added a new format to vlink, which can output segments (e.g. Chip-RAM and Fast-RAM) controlled by a linker script. It also writes a simple relocation table on demand (1st word number of relocs, followed by n reloc offsets) for each segment, which I use to relocate my program when starting it out of the boot block. |
|
23 October 2013, 11:21 | #9 |
Glastonbridge Software
Join Date: Jan 2012
Location: Edinburgh/Scotland
Posts: 2,243
|
currently thinking about writing a macro that maintains external jumps in a linked list, that I can traverse on loading the module, look up the references and replace them.
Something like Code:
xjmp MACRO dc.w $4EF9 xj_\@ dc.w xj_last-xj_\@ dc.w \1-xj_\@ xj_last set xj_\@ ENDM Code:
xjmp myFunc myFunc dc.b "myFunc",0 |
23 October 2013, 14:27 | #10 |
Natteravn
Join Date: Nov 2009
Location: Herford / Germany
Posts: 2,500
|
Interesting technique, although you still have to define all these cross module calls manually. I assume you also have a kind of linked list for exporting function labels?
Your usage of the SET directive might cause different results with different assemblers. AsmOne, A68k and vasm will use the value of the last xj_last in the source for the first reference to it (making the difference 0 in your example with a single entry). So it would result in a circular linked list. Barfly, Devpac and PhxAss will print an error. |
23 October 2013, 14:37 | #11 |
Glastonbridge Software
Join Date: Jan 2012
Location: Edinburgh/Scotland
Posts: 2,243
|
I didn't mention it but I was going to put with something like
Code:
xj_begin dc.l 0 xj_last set xj_begin Last edited by Mrs Beanbag; 23 October 2013 at 14:43. |
24 October 2013, 22:32 | #12 |
AMOS Extensions Developer
Join Date: Jun 2007
Location: near Cambridge, UK
Age: 44
Posts: 1,924
|
Some interesting tips here. I agree with most. I certainly have started using macro's more in my current project and certainly need to stop using dc.w's etc.
When I'm debugging I always comment out the code I think is causing the problem and NEVER delete it until I'm sure the problem is fixed. Also, any modifications are put inside what I call an 'asterisk field' so I can clearly see new code vs. original code: (yes, I know some assemblers will automatically correct the following code, but I couldn't think of a decent example in such a short time) Code:
'bne.w jump1 ***\ bne.l jump1 ***/ Code:
*************************************************************************** * C L E A R S C R E E N * *************************************************************************** ; INPUT: a0.l = destination address ; d0.w = modulo (e.g 36 for LowRes) ; d1.w = size of area, in BLTSIZE format (%hhhhhhhhhhwwwwww) ; ; OUTPUT: None *************************************************************************** |
13 February 2015, 01:46 | #13 |
Banned
Join Date: Dec 2014
Location: Montreal
Posts: 129
|
This is more a general remark than an assembler specific one but one should always use source control.
With something like Git (or Perforce, which is free for personal non commercial use) you should push every single version of your code which compiles. This is a tremendous help when you need to figure out what exactly is it that you changed which broke your program about ten compilations before or just want to revert to a working, validated version. It's also helpful if you want to do several experiments and keep them for easy comparison: branches will allow you to modify the same code safely in isolated repositories which you can later merge back to the main branch once you have made your choice. |
13 February 2015, 10:45 | #14 | ||
Natteravn
Join Date: Nov 2009
Location: Herford / Germany
Posts: 2,500
|
Quote:
It also saves you from doing backups, provided the revision control system is on an external server which does automatic backups itself. Quote:
You may also not like to push your code onto a public server. Most people already have a NAS or a small Unix server which can be used locally for that purpose. |
||
13 February 2015, 10:48 | #15 |
move.l #$c0ff33,throat
Join Date: Dec 2005
Location: Berlin/Joymoney
Posts: 6,863
|
|
14 February 2015, 18:15 | #16 |
Glastonbridge Software
Join Date: Jan 2012
Location: Edinburgh/Scotland
Posts: 2,243
|
so you can get SVN and/or Git for Amiga? I would have problems using it because my Amiga thinks it's permanently 1992.
|
15 February 2015, 04:09 | #17 | |
Moderator
Join Date: Nov 2004
Location: Eksjö / Sweden
Posts: 5,604
|
Quote:
Also agree with Stingray. For my own part, I don't trust any version control system, I use versionitis instead. Which is to make incessant backups, naming the files with details, and also have changelogs in the files themselves, and reminders what I was doing so I can pick it up at any time. And it still doesn't work. There's no way Or, well, the best way would be to just update the files in one go until the project is finished, but it's sort of incompatible with "a smattering of available timeslots for coding". As an interesting point, it's when you do the overhauls (that are oh so innocent, just going to refactor this symbol or change a register globally) that you get hard-to-find bugs. "It's somewhere in there..." I have some basic things I do, which is mostly for consistency and ease of use/readability and not for project management: - colon after labels, so I can search for the actual routine and not occurrences of calls... - case insensitivity, so I don't have to look how I cased some symbol last time. For a source release I turn it on. Maybe. - macro names are caps, symbols are initial caps, local labels lowercase - straightforward names for symbols. just saves headscratching for some strange AbbrCamelC label - I comment too much. I know it's really good when you pick it up again, and well... I just do it. I do refrain from juvenile comments such as "BRAs should be eliminated, girl coders agree" nowadays. I think. If you find one sue me - variables that need initial values anyway I put as DC near the functions that use them. Unnecessary DS+initalization routines that are not called is just a too common reason for bugs. Plus it saves code. - some consistent styling of comments that is mostly for pretty presentation and not important. I think the only important tip I can (maybe?) contribute is to lock finished code into includes to only leave pertinent implementation code visible for editing. And should you ever change a common include, you make a fork (for me: copy include to project folder before changing). Last edited by Photon; 15 February 2015 at 04:15. |
|
15 February 2015, 07:14 | #18 | |||
Banned
Join Date: Dec 2014
Location: Montreal
Posts: 129
|
Quote:
They archive changes in an easily accessible manner which can be visualized graphically to show evolving diffs and timelapses. They just automate what you call "versionitis" in a much more convenient way. I have been using revision control systems for 14 years and there's no way I would go back, they allow me to reserve my brain cells for coding without being distracted by logging and tracking. Just as I leave the assembling of code to my assembler I leave the logging of my changes to the revision control system. Quote:
And you don't have anything to copy or any worries about mixing several revisions together by mistake. Quote:
SVN will work nicely for simple work but at some point you will want to use branches because they are a very convenient tool and that's where it will stab you in the back. It should be relatively simple to compile a version of Git for the Amiga using GCC. I don't think it should have any issue to run at decent speeds since it was designed to be fast. Also, I'm not sure RCSes require a coherent system time. Perforce doesn't care about it and just keeps tracks of your submissions to the server and Git likely does the same kind of bookkeeping internally, I doubt it relies on the time of operations, only on their ordering. (*) My company used SVN around 2007 and we lost a considerable amount of time because of its design issues when dealing with branch merges and the fact that it was dog slow (for our needs: several MBs of code per branch). I pushed them hard to switch to Perforce and although they were hesitant at the beginning they never regretted it. It's robust and reliable and blazingly fast. Last edited by TCD; 15 February 2015 at 09:17. Reason: Back-to-back posts merged. |
|||
15 February 2015, 21:16 | #19 |
Moderator
Join Date: Jan 2003
Location: ...
Age: 52
Posts: 1,838
|
Actually with the amount of assets we had for our games, converting IFF images on the fly would have been a nightmare, and completely unfeasible due to the extra number of disks required plus the extra amount of memory the game would have needed for buffering. Some of the assets were hundreds of KBs animated IFFs, so just recycling one of the off-screen buffers while clipping and converting the image would not have been possible without asking for a minimum of 1.5MB memory instead of 1MB.
Instead, we used to have a script driven converter that could generate anything we wanted using still pictures as well as animated IFFs etc, remove/add bitplanes (can save a lot of memory), play with masks, clip the graphics area to the minimum required size (and add information about the missing lines and rows so they could be re-generated) and so on... very cool stuff for its time. All it took to generate anything was to run the tool via DOpus and click on the script filename, which in turn could process any number of graphics files. Everything else mentioned in this thread was in place as well, out of necessity, ie using linker with separate object files, RS structures, tons of macros that somewhat resembled C code etc. We also had an in-game scripting for the entire game logic - otherwise it would have been a complete nightmare to create RPG games in assembly language, since those require a huge amount of game logic. |
15 February 2015, 21:26 | #20 |
Moderator
Join Date: Jan 2003
Location: ...
Age: 52
Posts: 1,838
|
Actually, we made everything we used to run from DOpus and scriptable, including converting the assets, compiling (from make files), compressing the assets/data/code, and creating master disks - and doing test runs of the new code from hard disk or floppy disk(s).
Debug code would return to DOpus once we quit from the running code (assuming it wouldn't crash...) while release code would never return. So we made DOpus our complete IDE |
Currently Active Users Viewing This Thread: 1 (0 members and 1 guests) | |
Thread Tools | |
Similar Threads | ||||
Thread | Thread Starter | Forum | Replies | Last Post |
Neat idea - borderless WinUAE and Amiga wallpaper | Bloodwych | Amiga scene | 8 | 12 January 2011 23:58 |
2000 - black screen... Chips good... PSU good... | chiark | support.Hardware | 45 | 09 January 2009 05:41 |
Mitser Org'oeil, good platformer in old style | s2325 | Retrogaming General Discussion | 2 | 23 November 2008 21:58 |
good retro racer in Lotus/Outrun style | s2325 | Retrogaming General Discussion | 4 | 27 May 2007 20:57 |
very good new racing PC game in old style | s2325 | Retrogaming General Discussion | 1 | 20 February 2007 22:34 |
|
|