Because of the current issue, I'm starting to investigate where I can switch bank(s) and how useful in that scenario it could be but I found one possible issue: one case where I may need to call the code from another bank and come back after since I may need to change more than 1 bank to do the operation. I guess this is all trampolines are about.
I understand the concept and why it would be useful but my only concern is if there are place where you have to be careful to use trampoline or not. For example, I may have to use some trampoline code to jump to the sound driver, inside the nmi, then continue once finished but is it dangerous to do so from NMI? If the sound driver is not over, the NMI won't return yet, right?
What are the "gotcha" that I have to be careful when using such techniques?
I think that switching banks for music code is a standard thing to do.
I'm using an MMC3. I had the sound driver and code/data for current level selected at all time and was using general code/data in both fixed bank but now I reached a point in complexity that it's not enough anymore. I was aware of people mentioning about switching the sound driver but didn't have the need "yet". Now, it's knocking on the door big time ^^;;
The reason I was concerned is that I'm using dynamically set NMI, which means the code reside inside the stage related bank and is set at runtime. Which means, if I need to switch the stage bank to get the sound driver and maybe the soundtrack, then I was afraid of the possibles issues. I think I need to try, no more choice now
Once I have something more concrete I will be more than happy to share it but for now it's not worth it, yet
This could work as well in a situation where there is a fixed bank, and the trampoline code would be in the fixed bank. The "magic" is that the calee doesn't have to know if it was called directly (from the same bank) or not, just doing "rts" will automatically switch the caller's bank back if necessary.
Code: Select all
;in the (real or virtual) fixed bank: CallDoSomething: lda CurrentBank ;remember the current bank's index pha lda #.bank(DoSomething) ;switch to the subroutine's bank ;OMITTED: bank switch code (depends on mapper) jsr DoSomething ;call the subroutine pla ;restore the previous bank ;OMITTED: bank switch code rts ;return to the original caller
You can also implement a generic trampoline, to which you can supply a bank index and the destination address somehow (via registers, RAM, ROM, the stack, whatever works best for you), but that'll obviously be slower than a dedicated trampoline.
A dedicated trampoline can also have extra code to preserve parameters and return values (which would normally be trashed by the bankswitch code), which might be important in some cases.
And a significant amount of the time, it can even be simpler than that, if you know that you only call a function from a certain bank. (ie I always call function FOO from bank 0). Then you can skip saving the current bank, and just hardcode the return bankswitch.tokumaru wrote:You don't normally need to manipulate the stack explicitly to implement trampolines. The basic implementation goes something like this....
That's my experience. In the one or two exceptions, it's sometimes easier just to make a new trampoline (ie CallFooFromBank1) than to bother with making a more generalized trampoline.tokumaru wrote:That's true. At least in my programs, most banked subroutines are only called from the main engine, meaning that the return bank is constant and doesn't need to be saved on the stack.
Since I want to try to switch the driver in nmi and the nmi code is inside the stage bank, since I know the bank will always be the same in that case, I can hardcode it.
Will try it soon and will sure have fun at first, failing doing it properly
Some crazy idea came out of nowhere but will mention it anyway since we are on the trampoline subject. If I wanted, I could write a template code dynamically in memory. Before calling the code, I update the bank and when the method would come back, it would be back at the original code, right? It seems convoluted but seems the best way to avoid bank issues while switching since memory is not bankswitched anyway and you could keep the template in ram at anytime. Why do I think those ideas, I guess I love pain ^^;;;
It can probably keep things simple because each bank is dedicated to a certain task (music, level data, sprite animation data, etc. all have their own bank).
There are some trampoline calls that seem pointless. It's possible the programmer planned on having the same call at a fixed address across several banks but it's redundant now.
I see, I never thought about it this way since my test have always been very limited and with not much data so I never had to think about micro-managing my data up to now. For example, my player's metasprite data was in the second fixed bank since I was sure that I would not overflow with code, am I, and that's the most important data ^^;;; Now, I realize I don't need that data in every state of the game (intro, menus, etc) so I should fix that.
This means, in my main loop for the stage, I would process input, prepare state for entity/player, check collisions, switch bank to get meta sprite data and put it the OAM buffer, switch bank to get map data if necessary and while in NMI, switch banks for soundriver and current stage music and revert previous bank. Rinse and repeat.
I guess before continuing I should test how to implement it. With ca65, I just need to change where the data is located and it will be fast to test.