-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: WIP: Use type-safe ids #1707
base: master
Are you sure you want to change the base?
Conversation
Wouldn't it be cleaner to use an enum type? No need for an extra class, while still providing the extra strictness and arbitrary size. |
Yes, I also don't quite see the advantage of this approach over simply changing One other thing I noticed: we really should give OBJECTTYPE a constructor that simply calls CreateItem(). |
I disagree. An enum class implies that there is a known number of known variants. I would argue we currently only have the known number and that also only holds as long as we don't allow adding any additional items in mods. What the actual item id points to is known only after loading the |
I wouldn't get stuck on the fact that it's an enum, since it'd clearly be used as a type. Just like nobody would find the underlying ugliness of your class approach, unless they looked specifically. ;P |
@@ -2114,7 +2114,7 @@ static void AddNewItemToSelectedMercsInventory(BOOLEAN fCreate) | |||
if( !(GCM->getItem(gpSelected->pDetailedPlacement->Inv[ gbMercSlotTypes[ gbCurrSelect ] ].usItem)->isAmmo()) ) | |||
gpSelected->pDetailedPlacement->Inv[ gbMercSlotTypes[ gbCurrSelect ] ].bStatus[0] = (INT8)(80 + Random( 21 )); | |||
|
|||
if( gusMercsNewItemIndex ) | |||
if( gusMercsNewItemIndex != ItemId(NOTHING) ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You already defined NOTHING as an ItemId.
} | ||
|
||
|
||
static void InitBobbyRayUsedInventory(void) | ||
{ | ||
UINT16 i; | ||
ItemId i; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd use the opportunity to move this into the for init-statement, here and in the other places.
@@ -1359,7 +1359,7 @@ static void LoadSoldierStructure(HWFILE const f, UINT32 savegame_version, bool s | |||
if (!active) continue; | |||
|
|||
//Read in the saved soldier info into a Temp structure | |||
SOLDIERTYPE SavedSoldierInfo; | |||
SOLDIERTYPE SavedSoldierInfo = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SOLDIERTYPE already has a default constructor because of the ST::strings and other stuff it contains, so this does nothing useful.
@@ -2533,7 +2533,7 @@ static UINT8 GetUnusedMercProfileID(void) | |||
|
|||
static void CreateTempPlayerMerc(void) | |||
{ | |||
SOLDIERCREATE_STRUCT MercCreateStruct; | |||
SOLDIERCREATE_STRUCT MercCreateStruct = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The line below is now not needed anymore.
@@ -560,7 +560,7 @@ void QuestDebugScreenInit() | |||
|
|||
gItemListBox.usItemDisplayedOnTopOfList = 1; | |||
gItemListBox.usStartIndex = 1; | |||
gItemListBox.usMaxArrayIndex = MAXITEMS; | |||
gItemListBox.usMaxArrayIndex = MAXITEMS.inner(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you really want MAXITEMS to be an ItemId? size_t looks like a better choice to me.
@@ -38,7 +38,7 @@ void ExtractObject(DataReader& d, OBJECTTYPE* const o) | |||
EXTR_U8(d, o->ubGunAmmoType) | |||
EXTR_U8(d, o->ubGunShotsLeft) | |||
EXTR_SKIP(d, 1) | |||
EXTR_U16(d, o->usGunAmmoItem) | |||
o->usGunAmmoItem = d.read<ItemId>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're doing this often enough that adding EXTR_ItemId and INJ_ItemId macros are probably worthwhile
@@ -259,7 +259,7 @@ void LoadWorldItemsFromMap(HWFILE const f) | |||
{ | |||
// Add all of the items to the world indirectly through AddItemToPool, but | |||
// only if the chance associated with them succeed. | |||
WORLDITEM wi; | |||
WORLDITEM wi = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The very next line overwrites this initialization.
// as the type Inner an can be explicitly converted from / to it. | ||
template <typename For, typename Inner> struct TypedId { | ||
public: | ||
constexpr TypedId() : m_id(0) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you add something like using TID = TypedId<For, Inner>;
here you don't have to repeat it all the time.
return m_id != rhs.m_id; | ||
} | ||
|
||
TypedId<For, Inner> operator++(int i) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drop the i after int.
} | ||
|
||
TypedId<For, Inner> operator++(int i) { | ||
auto copy = TypedId<For, Inner>(this->m_id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or just auto copy = *this
?
I know I am just a passing stranger, but that's not true. There are a bunch of counterexamples from the standard:
The last one demonstrates that the committee sees scoped enums as opaque type aliases for integral types with associated constants. |
Interesting. I might have another look at that then. |
This introduces a type-safe ID for Items. It's a struct with a single field, so it should serialize the same as the underlying type, making it usable in save games and such.
Why?
Currently we always use the underlying type (in this case
UINT16
) or a type alias. This has the disadvantage that the type checker does not check whether what you are using is actually an itemid and not just anotherUINT16
(or even other types that are automatically converted toUINT16
). It also encourages things like iterating over item ids using++
and strict item id bounds such asMAX_WEAPONS
, which limits moddablility (what if I want to have more weapons in my mod).Continuing the refactor
This would require other entities such as weapons to use the
TypedId<>
template (seeTypes.h
) as well and refactoring the places where.inner
orItemId(x)
is used to use iterators.Implications
Most places using
.inner()
andItemId(x)
should be considered technical debt and should be replaced, e.g. by iterators.Happy for some feedback on this one.