It is currently Mon May 20, 2019 8:00 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 6 posts ] 
Author Message
PostPosted: Mon May 06, 2019 9:32 am 
Offline

Joined: Mon Nov 21, 2011 3:53 pm
Posts: 43
Hello my fellow nesdevers,

What is the best way to jump to an address held by a pointer?

The way I have it set up right now is that each of my gameobjects (16bytes each) that are held in a list in ram have their own update routine I call once during the NMI.
I could have just one ID byte for each gameobject and go through it like one big switch statement.

like this:

Code:
;; GameObject in ram:
.db #ID, ,,,,other values

;;;; When i want to update a gameobject:
;X holds ID
CPX #PLAYER
BNE .NotPlayer
JSR PlayerUpdateRoutine
.NotPlayer
CPX #ENEMY
BNE .NotEnemy
JSR EnemyUpdateRoutine
.NotEnemy
;; And so on.


I want to avoid this cause if the Gameobject happens to be on the bottom of the list it would waste allot of time.
Instead I was thinking about this:
Code:
;; GameObject in ram:
.db #ID,BehaviourAdrresLow,BehaviourAdrresHigh, ,,,,other values

;; Then in the update code I want to  jump to the address that is held in that piece of ram
;; This would also allow to easily swap behaviors if so desired.


What is the best way to do this?
At the moment I'm doing it like this:

Code:
;; in ram:
GeneralOpcode .rs 1
GeneralPtr .rs 2
GeneralOpcode2 .rs 1
;;;;;;

LDA #$20
STA GeneralOpcode ;; store the JSR opcode
LDA #$60
STA GeneralOpcode2 ;;Store the RTS opcode

   LDA $0500,y ;;holds the BehaviourAdrresLow
   STA GeneralPtr
   INY
   LDA $0500,y ;;BehaviourAdrresHigh
   STA GeneralPtr + 1

       ;;Store the returnadress on the stack
   LDA #High(ReturnLabel - 1)
   PHA
   LDA #low(ReturnLabel - 1)
   PHA
        JMP GeneralOpcode

ReturnLabel
;; rest of program


It works and I'm very happy it does but surely there must be a better way?

Thanks in advance!


Top
 Profile  
 
PostPosted: Mon May 06, 2019 9:56 am 
Online

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21387
Location: NE Indiana, USA (NTSC)
For how to call a function through a function pointer, see Jump table and RTS Trick in the wiki.

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Mon May 06, 2019 11:09 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11345
Location: Rio de Janeiro - Brazil
Your current solution looks a little too convoluted, with code in RAM and all... The idea is correct, but you can achieve the exact same effect in a much simpler way:

Code:
  lda $0500, y
  sta GeneralPtr+0
  lda $0500+1, y ;no need to INY since the base address is absolute
  sta GeneralPtr+1
  jsr JumpToGeneralPtr ;this allows you to RTS here

  (...)

  ;anywhere else in ROM:

JumpToGeneralPtr:
  jmp (GeneralPtr)

A JSR to an indirect JMP simulates an indirect JSR, which the 6502 doesn't have. This is good if the update addresses are static (never change during the course of an object's life), but if it does, the RTS trick would be a better solution, because you can get a new RTSable address from the game logic itself just by JSRing to a location that'll get the address from the stack and save it in the object's memory. Something like this:

Code:
 jsr CallObjectUpdate ;create a return point

  (...)

  ;somewhere else in ROM:

CallObjectUpdate:
  lda $0500, y
  pha
  lda $0500+1, y
  pha
  rts

And to modify the update address to the current location, and object can simply do:

Code:
  jsr ChangeObjectUpdate

  (...)

  ;somewhere else in ROM:

ChangeObjectUpdate:
  pla
  sta $0500+1, y
  pla
  sta $0500, y
  rts ;return to where the object was called

If you don't need to change the update address, just RTS directly instead of calling ChangeObjectUpdate.

I like this dynamic approach to the object routines a lot, mostly because it makes state transitions easy, because you don't need a lot of code to sort out what an object is doing, the code that handles the current state is called directly. This also saves a bit of ROM and logic because you don't need separate "Initialize" and "Update" jump lists for the objects, you just need each object's initialization address, and the object itself takes care of maintaining its update address.


Top
 Profile  
 
PostPosted: Mon May 06, 2019 11:51 am 
Offline

Joined: Mon Nov 21, 2011 3:53 pm
Posts: 43
That's so clever.
I knew there was a better way.

I knew about indirect jumping and tried it but didn't seem to work.
Apparently nesasm uses [] instead of () :oops:

Anyway thanks for the quick reply! :)


Top
 Profile  
 
PostPosted: Mon May 06, 2019 12:21 pm 
Offline

Joined: Thu Apr 18, 2019 9:13 am
Posts: 97
tepples wrote:
For how to call a function through a function pointer, see Jump table and RTS Trick in the wiki.


If you can afford dedicating 3 bytes of RAM to a springboard, and have fewer than 86 jump targets, have a page-aligned table of JMP instructions to each target, and a region of RAM that always holds $4C / target / JumpTableMSB. Then if X holds an object type, the code would be:
Code:
    lda objectProcs,x
    sta springboard+1
    jsr springboard

where objectProcs is a table holding the low-order bytes of the jump targets within the table. The extra three cycles for a jmp at the destination will be cheaper than the cost of having to set up both bytes of the destination pointer, and in many cases one could eliminate that cost if one includes a few small routines directly within the jump table, e.g.

Code:
NMIROUTINES:
nothing:
    inc frameCount
    rti
typicalThing1:
    jmp doTypicalThing1
typicalThing2:
    jmp doTypicalThing2
timeCriticalThing:
    ... code for timeCriticalThing goes here

Have the NMI vector point to a patchable JMP instruction in RAM, which will always point to something within a particular 256-byte page in ROM. Using this approach can shave a few precious cycles off the critical path, since one can eliminate the need to do any conditional tests before doing the time critical thing.


Top
 Profile  
 
PostPosted: Mon May 06, 2019 1:28 pm 
Offline

Joined: Mon Nov 21, 2011 3:53 pm
Posts: 43
supercat wrote:
tepples wrote:
For how to call a function through a function pointer, see Jump table and RTS Trick in the wiki.


If you can afford dedicating 3 bytes of RAM to a springboard, and have fewer than 86 jump targets, have a page-aligned table of JMP instructions to each target, and a region of RAM that always holds $4C / target / JumpTableMSB. Then if X holds an object type, the code would be:
Code:
    lda objectProcs,x
    sta springboard+1
    jsr springboard

where objectProcs is a table holding the low-order bytes of the jump targets within the table. The extra three cycles for a jmp at the destination will be cheaper than the cost of having to set up both bytes of the destination pointer, and in many cases one could eliminate that cost if one includes a few small routines directly within the jump table, e.g.

Code:
NMIROUTINES:
nothing:
    inc frameCount
    rti
typicalThing1:
    jmp doTypicalThing1
typicalThing2:
    jmp doTypicalThing2
timeCriticalThing:
    ... code for timeCriticalThing goes here

Have the NMI vector point to a patchable JMP instruction in RAM, which will always point to something within a particular 256-byte page in ROM. Using this approach can shave a few precious cycles off the critical path, since one can eliminate the need to do any conditional tests before doing the time critical thing.



Oh wow that even more clever.
Not gonna lie, every time I ask for help here I feel a little dumb afterwards. :lol:


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 6 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: Bing [Bot] and 2 guests


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 post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group