 |
nesdev.parodius.com NES Development and Strangulation Records message boards
|
| View previous topic :: View next topic |
| Author |
Message |
miau

Joined: 06 Nov 2006 Posts: 92 Location: Potsdam, Germany
|
Posted: Sun Aug 09, 2009 3:59 am Post subject: ca65 scopes and macros |
|
|
Dear experienced ca65 users,
I've run into a problem and hope you can help me.
particle.s:
| Code: | .scope peng ;"P"article "ENG"ine
;init - initializes particle engine
init:
...
rts
;run - called once per frame, loops through all particles and updates their position etc.
run:
...
rts
;particle_create - create a particle and return its index in the X register
.macro particle_create
...
.endmacro
.macro particle_setpos px,py
...
.endmacro
;updates the particle specified in the X register
particle_run:
rts
.endscope |
Now, what I can do outside of the scope is:
| Code: | jsr peng::init
jsr peng::run
jsr peng::particle_func |
However, using the macros like this:
| Code: | peng::particle_create #01
peng::particle_setpos #40,#120 |
doesn't work.
Macros seem to be directly accessible from all scopes(?), so just calling them without the "peng::" part works. That defies the purpose of scopes, though.
My questions: Is there any way around this? Is it even good practice to write code like this or is there a better, recommended way?
It's the first time I've been using scopes in this way, but I've got older similar code that I want to convert to be more readable and organized. |
|
| Back to top |
|
 |
Banshaku

Joined: 24 Jun 2008 Posts: 1108 Location: Tokyo, Japan
|
Posted: Sun Aug 09, 2009 5:18 am Post subject: |
|
|
This is what I found from the documentation:
------------------------------------------
Note: Macro names are always in the global level and in a separate name space. There is no special reason for this, it's just that I've never had any need for local macro definitions.
------------------------------------------
edit:
Found here, when they talk about scope. |
|
| Back to top |
|
 |
miau

Joined: 06 Nov 2006 Posts: 92 Location: Potsdam, Germany
|
Posted: Sun Aug 09, 2009 5:31 am Post subject: |
|
|
Ah, thanks. I've been browsing the documentation for a solution, but totally missed that bit about macros being global.
That sucks, looks like it makes no sense using scopes to wrap my modules then. |
|
| Back to top |
|
 |
Banshaku

Joined: 24 Jun 2008 Posts: 1108 Location: Tokyo, Japan
|
Posted: Sun Aug 09, 2009 5:50 am Post subject: |
|
|
| miau wrote: | | That sucks, looks like it makes no sense using scopes to wrap my modules then. |
It depends what you want to achieve. I will try to answer from my little experience with ca65. You want to "wrap modules". What does it exactly means? All your code is in one file and want to create some scope to avoid some name clash? I could try to figure out what you're looking for. That will give me more experience with ca65 at the same time.
The way I handle modules in ca65 is this way. You create many files for the modules you want (view module, music module etc). By separating by files you already create some scope, which is the file scope. Then, if you want other modules to see the view module init procedure, you export the init method only. You could always call the method viewInit (subViewInit, anyway that seems clear) to avoid any name clash. The same thing can be done for variables by defining them in that specific file.
For now this is the way I'm doing it. All methods/variables not exported will not be seen by my external modules. The linker will be in charge to export/import the right symbols and glue everything together. Defining scope with the scope symbol seems only useful if you want to create a local scope but still. hmm.. You could always create a scope inside your module, maybe export it and it will give it a C++ like look but I don't know how useful it would be. Already just using .proc/.endproc create a scope for the labels.
Just give me a little bit more information of want you want to do and I will see if I can find anything. |
|
| Back to top |
|
 |
miau

Joined: 06 Nov 2006 Posts: 92 Location: Potsdam, Germany
|
Posted: Sun Aug 09, 2009 7:01 am Post subject: |
|
|
By export and import do you mean having a c-like separation of source and header files?
Currently I've just got lots of separate source files for the different tasks that are included from the main source file. It works for small projects, I guess, but you can get dependency problems when one module tries to jump to code from another module and vice versa.
Importing only the functions I need would help. I take it you're refering to the .export and .import commands, if so: it should work with macros, too, right?
Still, I would have liked to use scopes to wrap the different modules.
Here's just a somewhat extreme example if I would be very pedantic with my naming scheme. If not using scopes the macro in my first post would actually have to look like this:
| Code: | | peng_particle_set_position |
peng is a module, particle is a sub-module, but "set" isn't. This is messy.
The scope operator helps make things clear:
| Code: | | peng::particle::set_position |
peng is a module, particle is a sub-module, set_position is the function.
I like keeping my function names as short and expressive as possible, but that doesn't always work.
Maybe changing my naming scheme might help. I.e. from underscore-separated identifiers to justleavingspacesoutentirely or CamelCase:
| Code: | | peng_particle_SetPosition |
What kind of naming scheme do you guys use? Are you strictly following it at all times?
This is something I've been contemplating over for a long time. I don't want to spend most of my time writing variable and function names while coding, plus, it sometimes takes me longer to grasp the essence of a piece of code if identifiers are too long (too much information, same goes for too heavy commenting which people new to programming tend to do). On the other hand, the functions, variables and code structure should be self-explanatory to the outsider.
I can't seem to find a balance between the two. Looks like you have to trade one thing off for the other. |
|
| Back to top |
|
 |
Banshaku

Joined: 24 Jun 2008 Posts: 1108 Location: Tokyo, Japan
|
Posted: Sun Aug 09, 2009 9:13 am Post subject: |
|
|
| miau wrote: | | By export and import do you mean having a c-like separation of source and header files? |
In a way, yes.
| miau wrote: | Currently I've just got lots of separate source files for the different tasks that are included from the main source file. It works for small projects, I guess, but you can get dependency problems when one module tries to jump to code from another module and vice versa.
|
If you code that way it will work but I think you will miss one of the advantage the cc65 linker give you.
What the import/export do is mostly define scope for your function/variables. It won't import code or anything, it will just tell that X symbol defined in a module is available at N address.
I will try to show an example of scope with import/export. Let say we have 3 modules: main, view an sound. Main will want to init the view and sound handler. Then in the nmi, it will want to play the sound. Here's how it could look like (warning, wall of code sample):
main.asm
| Code: |
.segment "ZEROPAGE"
zpGameState: .res 1 ; Only main can access this variable
; I'm importing a variable defined by the sound module, the music handler
.zpimport zpCurrentSong
.segment "CODE"
; I want to use these symbols from other modules
; (usually I will define a H file with the same name
; as the module that export the symbol)
.import subInitView
.import subInitSoundDriver
.import subPlayNextMusicFrame
reset:
; I decide to init the view with the symbol I imported
jsr subInitView
; Let's init the song with the variable I imported
lda #$03
sta zpGameState
; then init the song
jsr subInitSoundDriver
; do nothing for now
mainLoop:
jmp mainLoop
nmi:
; call the next frame on sound driver
jsr subPlayNextMusicFrame
|
Now let's check the content of the view manager view.asm
| Code: |
.segment "ZEROPAGE"
zpMyLocalVariable1: .res 1 ; Only the view can access this variable
zpMyLocalVariable2: .res 1 ; Only the view can access this variable
.segment "CODE"
; First export the symbols I want to be public
.export subInitView ; Only this symbol can be seen by outside module
; this method can be called by anybody who imports it
.proc subInitView
; do some stuff here
; call private method
jsr subViewInternalMethod
rts
.endproc
; Only view module can access this method, making it private
.proc subViewInternalMethod
rts
.endproc
|
Finally, the sound handler module:
music.asm
| Code: |
.segment "ZEROPAGE"
zpMyLocalVariable1: .res 1 ; Only music module can access this variable
zpMyLocalVariable2: .res 1 ; same thing here
zpCurrentSong: .res 1
.zpexport zpCurrentSong ; This variable is now viewable by anybody who decide to import it
.segment "CODE"
; First export the symbols
.export subInitSoundDriver ; this symbol can be seen by outside module
.export subPlayNextMusicFrame ; this symbol can be seen by outside module
; this method can be called by anybody who imports it
.proc subInitSoundDriver
; do some stuff here
; call private method
jsr subDriverInternalMethod
rts
.endproc
; this method can be called by anybody who imports it
.proc subPlayNextMusicFrame
jsr subMusicReadStuff
rts
.endproc
; Only music module can access this method, making it private
.proc subDriverInternalMethod
rts
.endproc
; Only music module can access this method, making it private
.proc subMusicReadStuff
; do things here
rts
.endproc
|
So now you may start to see a pattern. Every module reside in their own file. Each module can define their own private variable/function and if they need to give access to a variable/function, they will export it. If another module requires to use it, they will import it. This way, you reduce the chance of the main/view calling internal functions of the music engine and so it the opposite true. By defining your variable in the module, only that module can access that variable and will help reducing coding errors.
Each module could reside in different segments. Each segments could be in different banks. The segments will be set by the configuration file. Now by exporting symbols, you don't have to worry where is X variable/function etc: the linker will take care of it. Of course, if the segment is in a bank that is not available while calling it (you forgot to switch bank), it will fail but that's nothing the linker can do. That part is still your responsibility.
As for macro, I would be careful. Macro are not functions: they are just a way to avoid rewriting repetitive code. This mean they don't have scope. It's like a define in C. Every time you call it, that code will be included again, and again and again. With modules, the function will be called instead, which is better. Maybe a way to recognize a macro coulb be to use hungarian notation and put a prefix in front of it. I already do this for function (sub) and zero page variables (zp).
I can explain more in details if you want, now it's just that it's 1h in the morning, getting sleepy and I should be in bed since I have to work tomorrow, sorry ^^;;; |
|
| Back to top |
|
 |
miau

Joined: 06 Nov 2006 Posts: 92 Location: Potsdam, Germany
|
Posted: Sun Aug 09, 2009 11:08 am Post subject: |
|
|
Thanks, this method is much better than what I've been doing until now. I should have been using it from the start, really not much different from the way you do it in C, except for explicitly having to state .imports and .exports.
I like macros for one reason - parameters.
In my opinion the way you usually call functions can get confusing, especially if there are many parameters. you have to know the exact registers and memory locations they have to be stored in and it bloats the code visually.
Example with just a single parameter to keep it simple:
| Code: | lda #1
jsr particle_create |
vs.
I like creating macros to wrap functions that require parameters. So yeah, they usually don't contain the actual code, should have mentioned it.
| Code: | .macro particle_create id
.if .match({id}, a)
;a as parameter, do nothing
.else
;memory location or constant, just lda it
lda id
.endif
jsr particle_create_f
.endmacro |
This way they are flexible and the code you have to change if the memory location or register for a parameter changes is not scattered around the files, but in one central place. I can call them in different ways, so I don't have to waste space or cycles. The above macros handles two cases:
| Code: |
particle_create $10
lda $10
beq @skip
particle_create a
@skip: |
There might be cases where different parameters aren't easy to handle, that's when I just go for readability over cycle- or space-saving - at least until I hit the optimization stage in my project.
| Banshaku wrote: | | Maybe a way to recognize a macro coulb be to use hungarian notation and put a prefix in front of it. I already do this for function (sub) and zero page variables (zp). |
Yeah, I see the point. I just prefer hacky code with underscores in identifiers, because I'm used to it, I guess (hello unix gurus!). Let's see how other people are doing it. |
|
| Back to top |
|
 |
Banshaku

Joined: 24 Jun 2008 Posts: 1108 Location: Tokyo, Japan
|
Posted: Sun Aug 09, 2009 7:32 pm Post subject: |
|
|
For the macro, as long as you don't put too much logic in it that should fine I guess. I'm not using much macro yet that way, so maybe I should try. The more logic in it, the more change of missing an error thought. For public methods, I would define the macro in the header file.
| miau wrote: | | Yeah, I see the point. I just prefer hacky code with underscores in identifiers, because I'm used to it, I guess (hello unix gurus!). Let's see how other people are doing it. |
There is no best way. As long as you define rules, follow and understand them and feel comfortable with it then everything is fine. There will be always one group that prefer certain rules over others because they're used to it. Don't fall in those religion like wars about syntax
edit:
For the import/export thing, if you find it annoying to always import in every module you can do this with the assembler:
------------------------------
-U Mark unresolved symbols as import
------------------------------
This mean if a symbol is not found in the module, it will try to check automatically if there is an export somewhere. This is one way of doing it but hmm.. for now I prefer to explicitly import it, habit I guess.
edit2:
I found something interesting in my lunch time that may help you with the way you wanted to write code when not using macro thought. For example, let's take back the view module. Let's say the view module will export the init method and , I don't now, processFrame method.
Since init could be re-used elsewhere, you could have a name clash. What I didn't like is that I have to prefix every method with the module it's related. Now I found one solution. Instead of prefixing your method with view, do this in your h files:
| Code: |
.scope view
.import init
.import processFrame
.endscope
|
So in every module you have imported your h file, if you want to access the view public methods, you have to write:
| Code: |
jsr view::init
jsr view::processFrame
|
Still, I know, it doesn't process your parameters but I found that way of scoping interesting.
And I found that in macro you can check the count of parameter and make the compiler fail if it's not the right count. This part seems interesting. |
|
| Back to top |
|
 |
miau

Joined: 06 Nov 2006 Posts: 92 Location: Potsdam, Germany
|
Posted: Mon Aug 10, 2009 4:13 am Post subject: |
|
|
| Banshaku wrote: | For the import/export thing, if you find it annoying to always import in every module you can do this with the assembler:
------------------------------
-U Mark unresolved symbols as import
------------------------------ |
This is quite useful if you need to include the header file into the module it belongs to as well (if you've got macros or constants you need to share). Else, .export and .import clash and you get errors like "Symbol `xyz' is already an import".
Unfortunately, problems persist with the source/header file method.
I had hoped to get a bit more object-oriented-ness into my project with it, i.e. having private variables that only functions of the class have access to. So to change a variable you call a function. That seems to be common practice in OOP.
However, it means I can't use some macros as function replacements.
E.g. the following should be a perfectly valid task for a macro:
| Code: | .macro peng_particle_setpos px,py
.ifnblank px
lda px
sta p_x,x
.endif
.ifnblank py
lda py
sta p_y,x
.endif
.endmacro |
This would mean, though, that I would have to export the variables p_x and p_y as well... defies the concept of public/private members. Plus, to avoid future name clash, I would have to assign other, longer names to these variables - which is what I wanted to avoid in the first place.
It would be much easier if macros could be scoped. I understand I might be the only one who would like to use them in such a way.
The problem is never inside the computer, but sitting in front of it, as they say.  |
|
| Back to top |
|
 |
Banshaku

Joined: 24 Jun 2008 Posts: 1108 Location: Tokyo, Japan
|
Posted: Mon Aug 10, 2009 5:18 am Post subject: |
|
|
I guess for the includes and how you uses them is a matter of taste so I cannot say much about this.
As for macro, to my knowledge, the #define in C/C++ doesn't have any scope. I'm not aware of scope for macro in assembler (never did enough to confirm). Is it something that you want to reproduce that you where doing in another assembler?
To me, a macro is a pre-processor that will be included inside a file before it will be parsed. Seen that way, it make sense that the macro doesn't have scope since scope will be inspected during the parsing/assembling phase.
edit:
The only thing I could see to avoid the loading the parameters before every method is to compromise this way. For example, you make sure that all functions can only receive parameters with registers (when possible) and always in the same order. You define a macro that receive as a parameter:
- the name of the function
- Parameters 1 to 3
You have to define in every header file which parameters you can receive this way. When there is an exception, you have to document it. So your macro lpr (load ParameteR) could receive this:
lpr view::Init #$01, #$FF
So instead of calling "jrs", you call the lpr (Load ParameteR) macro to do the job. A function is just an address so it should not be an issue. What you have to make sure is the order of parameter is always the same.
An example. hmm.. I never write macro so I will write more like pseudocode:
| Code: |
; Order is anything you define in your rules
; Note: More validation could be done here too
.macro lpr target, param1, param2, param3
.ifnblank param1
lda param1
.endif
.ifnblank param2
ldx param2
.endif
.ifnblank param3
ldy param3
.endif
; finally, load the function
jsr target
.endmacro
|
That's one possiblity. Try to make it generic and follow some rules. Then, if you need some exception, you can create Load Parameter Variable (lpv) which receive the name of a variable with a value, 1 to N.
ex: lpv view::Init myVarName, $00, mayVarName2, $35
etc.
If you cannot do it one way because of the limitation, find a way around the limitation. That's what I would do. I don't know if what I said make sense but I will surely try it and see how it goes. |
|
| Back to top |
|
 |
tepples

Joined: 19 Sep 2004 Posts: 5824 Location: NE Indiana, USA (NTSC)
|
Posted: Mon Aug 10, 2009 6:27 am Post subject: |
|
|
| Banshaku wrote: | | To me, a macro is a pre-processor that will be included inside a file before it will be parsed. Seen that way, it make sense that the macro doesn't have scope since scope will be inspected during the parsing/assembling phase. |
Then what would be the direct counterpart to an inline function from C or C++? At this point, I'm pretty sure that's what miau was aiming for: something that's inline like a macro but scoped like a proc. |
|
| Back to top |
|
 |
miau

Joined: 06 Nov 2006 Posts: 92 Location: Potsdam, Germany
|
Posted: Mon Aug 10, 2009 6:30 am Post subject: |
|
|
You're right, being just a preprocessor command it makes perfect sense.
| Banshaku wrote: | | As for macro, to my knowledge, the #define in C/C++ doesn't have any scope. I'm not aware of scope for macro in assembler (never did enough to confirm). Is it something that you want to reproduce that you where doing in another assembler? |
No, basically, I want to find a way to reduce the confusion when working with parameters.
Also, in C/C++ you can declare functions as "inline" so instead of a function call the whole code is inserted into the program code whenever you call it. Gives you the speed of macros, but can still be inside a scope. Although I must admit that the concepts of macros and inline functions are completely different.
So yeah, what I'm REALLY trying to do with macros is emulate some of the comforts that higher-level languages offer to make code more readable and flexible in case of changes. I realize that might be a bit overzealous.
I'll just stick to my old method for now and move over to a scope-based system once I find a satisfying solution.
EDIT: tepples was right and faster than me  |
|
| Back to top |
|
 |
Banshaku

Joined: 24 Jun 2008 Posts: 1108 Location: Tokyo, Japan
|
Posted: Mon Aug 10, 2009 6:57 am Post subject: |
|
|
I read the complete ca65 doc today (always find new things on the next read when you have more experience) and don't remember anything that would be similar to inline function in C/C++.
I guess you will have to find a workaround for it. |
|
| Back to top |
|
 |
tepples

Joined: 19 Sep 2004 Posts: 5824 Location: NE Indiana, USA (NTSC)
|
Posted: Mon Aug 10, 2009 7:24 am Post subject: |
|
|
| miau wrote: | | So yeah, what I'm REALLY trying to do with macros is emulate some of the comforts that higher-level languages offer to make code more readable and flexible in case of changes. I realize that might be a bit overzealous. |
You could always write your own extended assembly language that compiles to a .s file for ca65. That's arguably what Dennis Ritchie did in 1972 when he invented C. |
|
| Back to top |
|
 |
Banshaku

Joined: 24 Jun 2008 Posts: 1108 Location: Tokyo, Japan
|
Posted: Mon Aug 10, 2009 10:45 pm Post subject: |
|
|
Regarding the comment of making scope to avoid name clash: doesn't work. You need to import first and if you have 2 init method, it will not know which one to put in that scope you want to create.
My mistake. You can rename an export symbol but you cannot do it for import. If it was possible, my idea would have worked.
Oh well. I will forget this concept.
Edit:
I found a work around. Let say I have my Sound driver called SoundManager. In the export, I could do this:
| Code: |
.export __SoundManagerInit := subInitSoundDriver
.export __SoundManagerPlayNextFrame := subPlayNextSoundFrame
|
I export the routine in a way that usually I would never uses. If I would see some code using __Method, I know someone didn't use it properly, kind of. You can call it if you want and it will work. It just that it will not conform to the rules you defined to use the module.
Then, in the header file, I would do this:
| Code: |
;
; Symbols that can be imported from the music manager
;
.import __SoundManagerInit ; X: current song
.import __SoundManagerPlayNextFrame
.scope SoundManager
Init = __SoundManagerInit
PlayNextFrame = __SoundManagerPlayNextFrame
.endscope
|
As you can see, I defined some symbols in that scope with the value of the import. Basically I'm just saying that in the scope SoundManager, the symbol Init is actually the address of __SoundManagerInit.
So after making it work, I tried to do the same thing with a macros. No luck. this mean macros have no symbols/address and are really parsed before the compiler do the job.
If I find a trick to simulate inline methods, I will let you know. What I shown may seem extreme for some people but I'm starting to like it. It just a way of writing code after all. |
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|