Entity data
Author(s): Lizzie, catinsuranceTags:
You may have read the guide on saving and storing data, which covers how to export data to a save file and have it persist between runs. This tutorial covers a similar concept that can be combined with the knowledge of saving data: creating data associated with objects.
GetData
and its drawbacks⚓︎
Isaac provides a simple way of storing data within entities: a method known as GetData. Utilizing GetData
is quite simple, as it can be referenced from any kind of Entity
. GetData
simply returns a table which can be used to store any amount of arbitrary data and access it later.
Although GetData
is convenient, it has several drawbacks which should be taken into consideration by modders. The docs provide some reasoning for this, citing that the data table is shared, volatile, and will require its own saving and storing regardless of it being a feature of the API, but these can be upsides depending on the use case. Here are a few additional drawbacks:
Performance issues⚓︎
GetData
creates a small amount of overhead when called. Because it is calling the C++ side, it disrupts the flow of code. Storing the tables on the Lua side would allow for better flow and management of code. Additionally, this overhead can stack up if the method is called consistently. If you still wish to useGetData
, avoid calling it multiple times within the same function, and structure your code to be able to pass it to lower level functions instead of calling the method again.GetData
does some amount of memory allocation on the C++ side, meaning its impact on performance can range depending on the speed and state of the user's memory.
Unexpected behavior⚓︎
GetData
does not account for items such as Glowing Hourglass. It may be hard to store a backup of data, as it must either be done in a sub-table or already stored locally.GetData
is erased from an entity onMC_POST_ENTITY_REMOVE
, which runs beforeMC_POST_NPC_DEATH
.
Depending on the use case, GetData
may still fit the needs of the modder, providing the convenience of being able to store smaller and simpler data that doesn't necessarily need to be scalable or need a complicated system. In cases where data needs to be scalable or there are issues of performance, it may be worth writing a simple system to store data locally on the Lua side.
Entity data holders⚓︎
Conversely, writing a entity data holder has many upsides:
- Because the system is in the user's control, the user can control when the data is cleared.
- It can handle specific and custom behavior, such as behavior for instantiation, or whenever the data is cleared.
- Any issues or edge cases can be handled by the system through custom behavior.
While writing a entity data holder is fairly straightforward, there are still a few considerations:
- The user must find a way to store the data in relation to specific entities or objects.
GetPtrHash
is a good starting point, but has several issues: BecauseGetPtrHash
returns a hash based on a pointer, when that memory is eventually cleared, the pointer will be reused. This means that if data is not cleared ahead of time, a new entity or object may reference the data of an old object by accident. - The data must be cleared manually, otherwise it will continue to stack up and cause a memory leak. This is also important for avoiding the previous issue.
In cases where writing a data holder might not be worth the time and effort or seems undesirable, it may be worth sticking to GetData
.
Writing a data holder⚓︎
To get started, simply create a new Lua file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
As it is just a holder, you may require
this Lua file so that the data is persistent across the project. Using include
would make the data local to the file you are including it in. See the "Additional lua files" article for more information about the differences between the two.
We use an EntityPtr object to keep track of the entity. Storing the entity directly can cause unexpected behavior, such as the variable pointing to a different entity when the initial one is removed. EntityPtr
is much safer and also clears its Ref
property automatically when the entity no longer exists. You can check for if Ref
is nil
to see if the entity still exists.
Clearing data in your holder⚓︎
Lastly, we will cover removing and clearing custom data. Additional callbacks should be registered to clear the information after specified events. Depending on the use case, this can range from clearing data when entities are removed directly in MC_POST_ENTITY_REMOVE
, clearing data when the entity is fully dead in MC_POST_NPC_DEATH
, or clearing entity data at the end of the room with MC_POST_NEW_ROOM
.
Below is an example of how to clear custom data on MC_POST_NEW_ROOM
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Similarly, the following code clears entity data on removal or death:
1 2 3 4 5 6 7 8 9 10 11 12 |
|