Question about object management (indirection / performance)

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Question about object management (indirection / performance)
by on (#241266)
Hello guys!

I'm pretty new to NES development and tried creating my first dynamic object manager.
Each object contains of 16 byte (x,y,sprite_address_high,sprite_addres_low,sprite_count,update_address_high,update_address_low, <some object related variables > )
I built a generic update function that jumps to the update address defined in update_address_low and update_address_high for each object sitting in the buffer.
Whenever I want to instantiate a new object at runtime, I just add one to the buffer and it's update will be called automatically.

Here is my problem / question:
I'm using a variable that points to the current object in the buffer. It is increased automatically after update of the current object, pointing to the next object.

Whenever I want to change a value of an object, I need to access them like this:

Code:
;game header / variable definitions
OBJECT_BUFFER = $0100

;SomeObjectUpdate
;Move object's X and Y position (X = first entry, Y = second entry)
ldx current_buffer_index
lda OBJECT_BUFFER,x
adc #$01
sta OBJECT_BUFFER,x
clc

lda OBJECT_BUFFER+1,x
adc #$01
sta OBJECT_BUFFER+1,x
clc


While developing I noticed that I have a high amount of indirection because I can only access values by caching the buffer position and add an offset to it.
Do you think it's a good idea? Will I get performance problems soon? Can it be optimized?

Thank you! :D
Re: Question about object management (indirection / performa
by on (#241267)
You should have a hardcoded thing for the player, it's not like you don't know how many players will be there.
Re: Question about object management (indirection / performa
by on (#241269)
thanks for your reply.
I already thought about that but i want to keep things consistent.
My idea was to create a system that I can apply to any future project. Like a template.
I don't know if it's a good idea for such a limited system
Re: Question about object management (indirection / performa
by on (#241270)
The way I've typically seen this done is to use a separate array for each variable, which is indexed into with the object's index. So, here, you'd have:

Code:
LDX current_object_index
INC object_x,X
INC object_y,X

(FYI, in your code sample, you're doing CLC after the ADC's; maybe this is intentional, but it looks like you'd have a problem with the first add possibly adding 2 based on the value of C.)

Also, having the handler's address as a variable kept with the object (update address low/high) seems flexible, but I would probably opt instead to have object IDs that are used to index into a handler jump table. The reason I like this approach is because you can reuse handlers for multiple objects and check the ID for any object-specific behavior within those handlers.
Re: Question about object management (indirection / performa
by on (#241271)
Ok I understand. So I create an array for each object attribute, with the length of the maximum object count?

Code:
object_x:
  .db $00,$00,$00,$00,$00,$00,$00,$00

object_y:
  .db $00,$00,$00,$00,$00,$00,$00,$00

;keep going


I barely know about CPU cycles and the cost of instructions. Would this method be less wasteful than my first approach?
Thanks so far!

EDIT: Well I don't need to 'create' them. the memory is already there :lol:
Re: Question about object management (indirection / performa
by on (#241272)
Yes, you'd allocate arrays each with a size equal to the maximum number of active objects.

Performance-wise, I'm not sure there's a big difference between the two. Both of these are doing indexed accesses, so your loads will be 4 cycles (or 5 if cross-page) and your stores will be 5 (or 4 if your array is in zero page). If you wanted to have more object memory than can fit into a page, that's probably where you'll start to see more benefit from splitting these out into individual arrays, since you wouldn't be able to just use object_buffer,X anymore. You'd probably be using (indirect),Y, which is 5 or 6 cycles. Adjusting current_object_index by 1 will also be a little faster than adjusting current_buffer_index by object size. The object buffer has the nice benefit of making it easy to initialize an object because all the bytes are contiguous, but loops add cycle overhead over just setting all the fields individually. The differences are mostly small, though this is one of those cases where small savings can have real benefit because they add up across all of the active objects.

That said, I suspect that whatever you choose here is unlikely to make or break your game's performance.
Re: Question about object management (indirection / performa
by on (#241275)
Just to be pedantic...

Quote:
OBJECT_BUFFER


I feel like this is an incorrect use of the word buffer. To me, when you want to pass a chunk of information from one place to another, but you have to wait for some reason, you store it in a buffer.

For example. If you want to pass Sprite data to the OAM, you would store it in a buffer and do an OAM DMA during v-blank.

Or, it you wanted to optimize transfers to the PPU, without turning off the screen, you would store PPU writes in a buffer, which can be quickly passed to the PPU during v-blank.

General, persistent information would just be an array of objects, or several arrays.
Re: Question about object management (indirection / performa
by on (#241276)
ExtremeCheddar wrote:
Code:
object_x:
  .db $00,$00,$00,$00,$00,$00,$00,$00

object_y:
  .db $00,$00,$00,$00,$00,$00,$00,$00

;keep going

You wouldn't normally use .db in this case though, you'd just reserve the space in RAM to hold N slots for each property (with N being the maximum number of objects that can be active at any given time). The .db directive is for when you need to specify actual values for things, and you have to copy these values to RAM yourself in case you need to manipulate them (you can't .db in RAM). The memory for active objects should be initialized to an empty state (e.g. using an object ID of 0), and slots should be filled and emptied dynamically as the player progresses through the game and objects are spawned/despawned.

When spawning an object, the data for it can either be generated on the fly (if you're into randomized enemies and such) or copied from a big list of objects in ROM (which can be created using .db statements) containing the main attributes (e.g. ID, coordinates, maybe one extra parameter) for all objects in a level.

If you want your objects to retain any kind of dynamic information after they are despawned (for example, whether an enemy has been killed or an item has been collected so you can avoid spawning them again), you need to reserve some RAM for that. I like to reserve at least 1 bit for each object in the level, in the same order as the objects appear in the full list of objects, so I can keep track of their alive/dead status, but I have an extra block of RAM (128 bytes or so) for objects that need to remember more than that. A byte in each object's definition indicates the index of the first byte of RAM they can use, and how many they use varies depending on the object's type.