This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Project Organization

Project Organization
by on (#40920)
So I have mainly one big project I'm working on, and it's going pretty well in some spots, and not so well in other spots. Having no real training on game programming, and starting out on the NES, this whole time I haven't been keeping my projects organized like a game designer should.

Until a couple weeks ago, I had my projects all located at a pretty long directory (in a folder in a folder in a folder in a folder in a folder on my desktop). So recently, I moved my game projects to C:\Game Projects.

So all of my NES projects are in C:\Game Projects\NES. Then it branches out to the projects, so all the stuff for ChateauLevania would be at C:\Game Projects\NES\ChateauLeVania. This is a much shorter directory than it was before.

Then comes the part where I might be messy (I don't know what other people's projects are organized like, so I could be wrong).

I have WLA-DX, and a main asm file with some other asm files which are basically lists of .include statements. Luckily, I've figured out how to use relative include directories so in C:\Game Projects\NES\ChateauLeVania" I could type:

.incdir "Data"

To change the include directory to "C:\Game Projects\NES\ChateauLeVania\Data". The advantage of this is that I can move the project folder to anywhere without having it looking up a directory that doesn't exist, because it's relative to the working directory.

So then I have "Code" and "Data" folders. The "Code" folder branches out to a couple more folders, but the big folders it branches to are "Window" and "Game World". In the "Window" folder are writes to all PPU/APU registers, where all the code that puts stuff on screen/makes noise is. The "Game World" folder contains all the code that's just game logic and whatnot. There's also a folder that contains useful macros like SetScroll and things that I'll use all the time. Of course, the folders in the "Code" folder have folders in them for specific subgroups and stuff.

"Data" is set up the same way. It contains all game data like maps and graphics in different folders. There's also AI which is made of code, but AI to me is data, regardless of whether or not it contains 6502 instructions.

So I ask, how are game projects usually set up? And is the way I'm going about it sloppy? Do I have too many folders, too little, or should I be going about it in a different way?

by on (#40922)
Organize with whatever works best. I mainly make directories for data that has many parts to it.

I have directories for Level and tileset data, enemy data, music, and CHR data. Everything else seems general enough to stick in the root directory.

But there's not really any standard.

by on (#40924)
I suppose there really isn't a standard, but I imagine people take more advantage of the assembler than I do. I seriously do almost nothing with the assembler. The only asm file that is part of the command line is main.asm. It takes header.bin and slaps it onto the beginning of main.asm assembled. Of course, main essentially includes lots and lots of files.

Should I be using my assembler to link more things together rather than them being linked by .include statements? I think I was reading a while ago that when making big programs in C, you want to link files together in the command line and not with .include statements.

by on (#40927)
Celius wrote:
Should I be using my assembler to link more things together rather than them being linked by .include statements?

Assembling to object files and then using the linker ('ld65' in CC65 or whatever it's called in WLA-DX) can improve encapsulation by hiding symbols that don't need to be used outside of a translation unit, which allows a programmer to keep track of fewer side effects. In addition, a build tool like GNU Make can be set to recompile only those object files whose corresponding source code has changed.

Another thing I do in newer projects (RAC, Lockjaw) is put my source files in src/ and object files in obj/nes/ (or obj/win32/ or obj/gba/ or obj/ds/), and the makefile can be written to toss the files where they go.

Celius wrote:
I think I was reading a while ago that when making big programs in C, you want to link files together in the command line and not with .include statements.

True. That's one of the things the regulars on gbadev.org pound into newbies' heads, and for good reason.

by on (#40930)
Is there any specific reason that you really don't want to use .include statements in a big C program in particular?

by on (#40939)
Celius wrote:
Is there any specific reason that you really don't want to use .include statements in a big C program in particular?


It depends how you look at it. "Technically", C programs have include statement but not in the same way that WLA do it.

In the case of WLA (and other assembler that uses include statement), it's like if you copy/pasted the code directly in your main file: so you have basically one huge file at the end. In the case of C, it will include a header file that will tell what the file have access to.

The problem with includes is function and variable have global scope: this mean function have access to everything. In assembler (and maybe in 6502 on the nes), this may seems less an issue because the amount of memory is limited and you may know about all the address range etc but sometime there is some variable that you don't want function A to have access to: only function B should be able to access it.

By creating modules like with CC65 (maybe WLA can do it), you can decide the scope of your variables and function. This way, you could have a variable , let say, myCounter in module A and only module A could read it. So module B will not see it, reducing the risk that some day module B modify the content of a variable that it's not supposed to. And you could have a helper function for A that only A should call but not B since it does some specific job for A only.

Put it simply, you want to give scope to your variables/function to reduce the amount of possible future coding error. By putting your myCounter in an include, anybody can use it. this is maybe not what you want in the first place. By defining the scope, the variable and/or function will be accessible in that scope only. In a C program, if the module is very huge, the change of someone using that global variable and putting the wrong data by accident is bigger so you have to be careful about who access what (especially if you're many people working on the same project).

If you want to see an example of scoping, you could always look at my old sample I made long time ago. this could give you some idea on how to do it. But be warned that I'm using that sample for learning 6502 and to try to find (like you're doing right now) how to put things in the right order for that platform. So you don't have to follow what I do exactly, just reading it may give you some ideas on how to define your own way of organizing your project.

by on (#40940)
In WLA, this is how I simulate local variables:

.DEFINE Counter ZTempVar1
.DEFINE XHolder ZTempVar2
function:
lda blah
cmp blah
bcc whatever
...
do something with Counter and XHolder
...
rts
.UNDEFINE Counter, XHolder

WLA allows you to define the values of variables for a certain section of code. By the way, ZTempVar1 is the first temporary variable in Zero Page of the section I assigned to them. It's basically a local variable that any routine can use as a counter, or just somewhere to save a value temporarily.

Once you Undefine them, you can use the names "Counter" or "XHolder" anywhere else in the code. This is really handy, because it's nice to have really really general names for some things.

by on (#40947)
Well, I have the sound code (with it's music and SFX data) assembled separately in WLA-DX than the rest of the game, I guess that makes 2 modules.
Until not so long I used to have one really big "main.asm", but evenutally it became really tedious finding stuff in it, so I split it in smaller asm files with .include directives (it's still quite big but it's much easier to find stuff in it).

However, it seems I can use labels from main.asm in sound.asm and vice-versa without using any special import or export commands, so even if they are assembled separately maybe they're still not 2 different modules. Anyway I'm pretty sure WLA-DX is much less flexible than CA65 when it comes to that, but there is other things that WLA can do which are usefull, and that maybe CA65 can do too but I'm less sure (it has good provisions for ROM and RAM bankswitching).

I can never use local variables in assembly because that's just not a possible thing without having a C-like stack, which is ackward to implement and ineficient. But doing this sure makes programming more structured and easier.
It's also great to be able to definie variables and be sure it may not be erronously overwritten by a part of the programm that isn't supposed to, but again in assembly all code have acess to everything so there is no real way to go arround that. (even if labels aren't acessible, you could just overwrite vraibles erounously with indexed or indirect adressing).
Also I guess the big advantage is to be able to reuse the same names again (something genreal like Counter) in many different routines, and all the counters are separate in real memory. In assembly you're typically forced to use the same counter (but then you can't use it more than once a time), or to have something like Counter1, Counter2, .... up to how many you need at the same time, which is annoying.

by on (#40949)
Yeah, the main bad thing about my code and using tempvars is that I can really only go to one function level, meaning I can't go into a function when I'm in a function. This is because in all of my routines, I use random tempvars that aren't accessed by a stack or anything. But I can still work with it. No math routines touch tempvars; they use variables that are strictly for them.

There's always the real hardware stack to do stuff with. Though I don't feel it's very safe to use some times, because if you push too much on it, some stuff maybe like addresses will get destroyed. Though this will only happen if you push like 200 bytes on and you've jumped to 30 levels of subroutines (JSR in JSR 30 times). Oh, and not to forget other things that are put on the stack. But in my case, I have 16 local variables (one for the game loop and one for NMI so there aren't any RAM conflicts). So I could really be using some of them in one routine, and if I need to go to a function which uses them also, then all I have to do is:

lda ZTempVar1
pha
lda ZTempVar2
pha

jsr Function
tax ;Returns with a value in A

pla
sta ZTempVar2
pla
sta ZTempVar1

The advantage of using tempvars in Zero Page is that they are really fast. So pla sta ZTempVar only takes 7 cycles to execute. However, in the long run this is a rather slow method (especially if you're in a loop). Then there's always just using the stack without any other RAM. But that has many disadvantages.

Most of the time, I'm not using all 16 tempvars. So I usually assign functions to use first ZTempVar1, ZTempVar2, then ZTempVar3, etc. all the way to however many it needs. So if a function only uses 5 of those variables, and I'm in a subroutine that needs to be using tempvars, I can use ZTempVar6-ZTempVar16 and still go to that function without worrying about RAM conflicts.

by on (#40951)
I was going back over this and was looking at the linking. I recall doing this for the stuff in my ASM class with MASM.

The only thing is that with the .include statements, everything seems to work just fine. Say you have your main file and then you have:
Code:
.include "file1"
.include "file2"


File2 would just start right where file1 left off. Is linking assembler specific?

by on (#40952)
I know, that's one of my main issues with using .include statements. There's always the .org statment to force it to start at a certain location:

.include "File1"
.org $C800
.include "File2"

Though for some reason, WLA seems to act really stupid when you try and just do .org $C800, it's all like "Can't org in a section" or something, and it's really stupid.

by on (#40953)
It's not stupid, it's because you're tring to org in a section, which doesn't make sense.
Sections are blocks of consectutive bytes you never wants to be taken apart. They are (likely) the equivalent of a module in CA65 (altough I'm not entierely sure).
It makes no sense to put a .org inside it.

by on (#40955)
Is it possible to do away with sections all together? I think you should be able to specify where a certain piece of code is starting without having to start new sections and stuff.

Say I have some tables that are each $80 bytes or less that I want to start at $8680, but I want them to each start on multiples of $80, so I never cross a page boundary. So it would be really nice to be able to do something like this:

.org $8680
;define table 1
.org $8700
;define table 2
.org $8780
;define table 3
etc.

Like I said, some of them may be less than $80 bytes, but they do not exceed $80 bytes. It would be really handy if you could just specify where the following code/data begins without dealing with sections.

by on (#40956)
You can't with WLA, but what's the big deal of dealing with section ? Place each table into separate section, and align them to $80 and you're done.

by on (#40957)
It really isn't that big of a deal, you're right. But I just think it'd be quicker to type .org $8680 instead of the stuff to close a section and open a new one. But this isn't that big of a deal.

For project organization, it's possible to have one file that organizes everything without doing anything special with the command line. It could look like this:

Code:
.bank 8 SLOT 1
.orga $C000
.section "FixedBank" FORCE
.incdir "IncludeFiles"
.include "FixedInclude.asm"
.ends

.bank 7 SLOT 0
.orga $8000
.section "MapData" FORCE
.incdir "IncludeFiles"
.include "MapInclude.asm"
.ends
...

.bank 1 SLOT 0
.orga $8000
.section "CHRRAM1" FORCE
.incdir "IncludeFiles"
.include "CHR1Include.asm"
.ends


.incdir makes the include directory (workingdirectory)\IncludeFiles.

This file points to files which include all the necessary data/code for particular banks. So see in Bank 1, CHR1Include.asm is included. This file would probably look like:

Code:
.incdir "Data\Graphics"
.incbin "CHR1.chr"
.incbin "CHR2.chr"


This way, all you'd have to assemble is this one file that bundles it all together. Though it might be too organized where it's not really intuitive, but it makes the working directory really uncluttered.

by on (#40959)
Bregalad wrote:
Sections are blocks of consectutive bytes you never wants to be taken apart. They are (likely) the equivalent of a module in CA65 (altough I'm not entierely sure).

Or are WLA "sections" more like CA65 "segments", which correspond to GCC "sections"?

Celius wrote:
Say I have some tables that are each $80 bytes or less that I want to start at $8680, but I want them to each start on multiples of $80, so I never cross a page boundary.

In CA65, that's what the .align keyword is for.

I find it cleanest to keep the meaning of code and its location in PRG ROM separate, for much the same reason that the modern WWW uses HTML for the meaning of a document and CSS for the style: it allows one to be corrected independently of the other. This reduces how much you have to keep track of in your head, which is the whole reason we have computers. Yes, alignment is meaning, even though it incorporates some aspects of location.

Celius wrote:
For project organization, it's possible to have one file that organizes everything without doing anything special with the command line.

But then you can't compile multiple files at the same time, one on each core, unless this one file is structured like a makefile.

by on (#40961)
My one compiled file is structured where everything is assigned a location. So with .DEFINE and .UNDEFINE statements where I can reuse variable names for certain parts of code, and that, I'm still not quite seeing why I wouldn't want to assemble it as one big chunk not dealing with the command line. It seems to simplify things.

Another way of organizing code would be to have one file like I said, but code and data wouldn't be separated, instead, it would be one bank to one folder. So the main "bundle" file would look like:

Code:
.incdir "bank0"
.include "bank0include.asm"
.incdir "bank1"
.include "bank1include.asm"
...


and bank0include.asm would look like:

Code:
.bank 0 SLOT 0
.orga $8000
.section "bank0" FORCE
.include "stuff.asm"
.incbin "whatever.bin"
...


and so on for whatever. This might be a better setup than separating code and data because you wouldn't get "lost" when trying to find something. As long as you know what bank it's in, you're good to go.

by on (#40962)
Celius wrote:
Say I have some tables that are each $80 bytes or less that I want to start at $8680, but I want them to each start on multiples of $80, so I never cross a page boundary. So it would be really nice to be able to do something like this:

.org $8680
;define table 1
.org $8700
;define table 2
.org $8780
;define table 3
etc.

Like I said, some of them may be less than $80 bytes, but they do not exceed $80 bytes. It would be really handy if you could just specify where the following code/data begins without dealing with sections.

As tepples said, there's .align. Some assemblers have a .pad directive, which pads the rom up to a specified address.

FWIW, I write my 6502 code just like you describe, one organizing file with a bunch of .includes, and smaller sections in their own .asm files. Banks are handled sorta like this...
Code:
.base $8000
.include bank0.asm
.align $2000

.base $8000
.include bank1.asm
.align $2000

.base $a000
.include bank2.asm
.align $2000

...etc

by on (#40966)
Yeah, I think this kind of system will work for me. I don't get really into dealing with the command line anyways :). I might reorganize my code to have a folder for each bank, with one include file. Though this could get ugly as I have 32 banks to deal will (512k PRG), but I suppose if I am viewing in icon mode instead of tile mode (I use Windows XP), it shouldn't look so cluttered and uninviting.

Also, I notice you do .Align $2000. What's $2000 for? What exactly does that do?

by on (#40968)
Celius wrote:
Also, I notice you do .Align $2000. What's $2000 for? What exactly does that do?

It makes each bank take up 8kB. Just a different way of doing things, without creating sections.

by on (#40971)
I use .org in CA65, often I have some code that I want to copy into RAM and run from there. So any labels in that are based on the .org address. At the end, you use .reloc and it goes back to the usual relocatable address mode.

This is different because it just links it normally, and you copy it to the ORG location yourself in your program.

by on (#40973)
For copying code to RAM, you could just assemble code telling the compiler that the starting address of the bank is $6000 (for SRAM) or $500 depending on wherever you want the labels to be. So you assemble that peice by itself so it turns it into a binary file, then you have that incbined into a particular location in ROM to copy to RAM. Oh, and that way you can really reuse names for variables because it's completely different asm file. But it's not so good if you jump to a location in ROM afterwards. But the ways to resolve this issue don't match up to the simplicity of your method.

I think someday I might look into CA65. I just used WLA-DX because it had more common features that NESASM was lacking (this statement is not meant to cause a branch in discussion to "how bad" people think NESASM is).

by on (#40979)
Celius wrote:
For project organization, it's possible to have one file that organizes everything without doing anything special with the command line. It could look like this:

Code:
.bank 8 SLOT 1
.orga $C000
.section "FixedBank" FORCE
.incdir "IncludeFiles"
.include "FixedInclude.asm"
.ends

.bank 7 SLOT 0
.orga $8000
.section "MapData" FORCE
.incdir "IncludeFiles"
.include "MapInclude.asm"
.ends
...

.bank 1 SLOT 0
.orga $8000
.section "CHRRAM1" FORCE
.incdir "IncludeFiles"
.include "CHR1Include.asm"
.ends


You'd want to have each subroutine or group of subroutine to have their own section. There is no point in having large piece of code taking up a large section, unless you really want it to be in the same order in your code and in the final binary file and there is no reason you'd want that. Exept for the sound code I place in a large section so that it's easy to crease a .nsf file.
If I would place it into many small section, the sound code would be shaterred in pieces in the large ROM, and it would be harder to make a .nsf file (I would basically have to take the ROM as a whole for the .nsf file, which would be a waste).

I use RAM code only once for a routine that maze sprites, so that it mazes them in either increasing or decreasing order, and only a few instruction has to be modified. I could do it all in ROM by placing a switch for two different routines, but it would waste more ROM I guess.
Altough WLA has almost no provisions for relocatable codes, which is lacking, I just made a RAM section with a label, and copy code into that label. When calling the routine, I use either the RAM label to enable sprite cycling, or the ROM label to disable it (if sprite priorities are needed). When modyfiyng the code, I just use RAMLabel,X, so I don't need any other labels, but that's not the clean way to do it, because if I change the code itself I have to change the routine that modifies it as well, and re-count the bytes it takes and so on...

by on (#40981)
one thing I've never liked about wla-dx is the .incdir command. I'd rather have it like in gcc where I can specify include directories as an argument to the compiler.

/rant off

I usually have my structure with a project dir as a root and sub-folders for code, assets, tools, etc. then I usually have all this connected to a svn so I keep a history of changes. So I can easily revert changes.

by on (#40984)
Bregalad wrote:
If I would place it into many small section, the sound code would be shaterred in pieces in the large ROM, and it would be harder to make a .nsf file (I would basically have to take the ROM as a whole for the .nsf file, which would be a waste).

Or you could set up your project such that it'd just be a matter of recompiling the project with only the sound code.

by on (#41012)
Celius wrote:
Is there any specific reason that you really don't want to use .include statements in a big C program in particular?

When you asked this, I posted this question at forum.gbadev.org. By now, they have come back with their replies.

by on (#41020)
Thank you for doing that. I still have to study the answers a little more, as I just skimmed through them. But this mainly applies to C/C++, and not 6502, right?

by on (#41023)
You only #include code in ASM if you don't use a linker.

by on (#41024)
You might use .include if you use header files and a linker. Exports + routine descriptions + constants go in header, routine definitions and data in source file.

EDIT: routine descriptions also go in header

by on (#41028)
blargg wrote:
You might use .include if you use header files and a linker. Exports + constants go in header, routine definitions and data in source file.


Totally agree with this. This is the way I'm doing it with CC65 for my nes projects.

by on (#41029)
I have header-esque files. They're mainly macro definitions though. I actually have kept my global variable definitions in separate files. They're a mess right now and could stand some cleaning up.