Skip to content
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

Implementation of Hercules Ultimate Storage System (HUSS) #3330

Open
wants to merge 32 commits into
base: master
Choose a base branch
from

Conversation

jasonch35
Copy link
Contributor

@jasonch35 jasonch35 commented Nov 4, 2024

Pull Request Prelude

Changes Proposed

  • Implementation of the Hercules Ultimate Storage System
  • Storages can now be created through a configuration file that describes their attributes.
  • Integrated storage_constants (Constant) into the configuration so that it can be assigned dynamically
  • Capacity is capped by MAX_STORAGE at config reading

Storage Configuration

{
        Id: (int)			Unique Identifier
        Name: (string)			Name of the storage sent to the client.
        Constant: (string)		Storage constant to be used by scripts
        Capacity: (int)			Maximum capacity of the storage.
}

Script Command
openstorage(<storage_constant>{, <storage_mode>});

Storage Modes

  • STORAGE_ACCESS_VIEW: View storage only.
  • STORAGE_ACCESS_GET: Allow retrieving items from storage.
  • STORAGE_ACCESS_PUT: Allow depositing items into storage.
  • STORAGE_ACCESS_ALL: (Default mode) Full access for viewing, retrieving, and depositing items.

All atcommands utilizing storage has been updated as well:

  • @storage <storage name/id>
  • @storeall <storage name/id>
  • @clearstorage <storage name/id>
  • @storagelist <storage name/id>

Issues addressed:

Upon testing the following has been fixed as well:

Credits to: @sagunkho and @dastgirp

Let's gooooo let's push this through!

This commit includes:
- HUSS main implementation
- openstorage script command update
- @storage atcommand update
- @clearstorage atcommand update
- @storeall atcommand update
- @storagelist atcommand update
- There can be an unlimited amount of storages and limits.
- All setting names are case-sensitive and must be keyed accurately.
- The storage capacity will default to MAX_STORAGE in mmo.h if it is set higher
- Storage ID 1 is the default (official) storage for accounts.
- Integrated storage_constants (Constant) into the configuration so that it can be assigned dynamically
@vBrenth
Copy link

vBrenth commented Nov 6, 2024

Thank you @jasonch35 hope this one will get implemented.
Also i want to ask about Storage Limit its still 600 correct?

@jasonch35
Copy link
Contributor Author

Thank you @jasonch35 hope this one will get implemented.
Also i want to ask about Storage Limit its still 600 correct?

Yes, Limit is capped by MAX_STORAGE which is 600 by default.

@jasonch35
Copy link
Contributor Author

jasonch35 commented Nov 7, 2024

Btw, I kept the "default" storage without warning/errors as it is set as default in original Herc's branch.
Commands and scripts will result in "no storage found" message if it is removed/changed.
Or parameter error on server start if its constant isn't found.
I believe server owners should be responsible for adjusting the configuration file, just as they would if they manually remove Red_Potion in item_db.conf, they'd have to manually remove them in scripts if they choose to do so.

Copy link
Member

@guilherme-gm guilherme-gm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for putting the effort in fixing the previous PRs!
I did not try running it yet, but from the code review, I got those questions/points that may need to be addressed.

@MishimaHaruna MishimaHaruna added this to the Release v2024.11 milestone Nov 30, 2024
Reverted to local variables since its not needed to allocate anymore.
-Direct initialization for struct instead to NULL
-Removed `intval` variable: copy value directly to `storage_id`
-Removed unnecessary message table comments
-Moved stst NULL check outside of if condition
- Check duplicate for non imported storage ID and overwrite when imported ID exist
- Move vector initialization on do_init_storage and transferred config reading after it
@vBrenth
Copy link

vBrenth commented Dec 20, 2024

I know its Holidays but please give us this, we've been waiting for years. (officially)
@MishimaHaruna


VECTOR_ENSURE(p->item, num_rows > MAX_STORAGE ? MAX_STORAGE : num_rows, 1);
if (SQL->NumRows(inter->sql_handle) > 0) {
VECTOR_ENSURE(p->item, MAX_STORAGE, 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be allocating more memory than needed if the storage is not full with MAX_STORAGE items. I think it would be better to keep the original logic of only allocating the needed space, never exceeding the MAX_STORAGE value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as Haru stated above #3330 (comment), number of items showing in the storage should retrieve actual (or more) amount even if its higher than storage capacity and should be hard limited only by MAX_STORAGE. Instead, we let map server to handle the limits. Which I agree in case owners reduce the capacity of a specific storage.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this approach of hard limiting to MAX_STORAGE only, what I mean is that the current code would always allocate MAX_STORAGE items, even if the storage only has 1 item (not as a limit, but as current slots in use)

Syntax of VECTOR_ENSURE:
VECTOR_ENSURE(<Target Vector>, <How many empty spaces must be there>, <How much to increase if there is not enough spaces>)

If you check current master code, you have this:

VECTOR_ENSURE(p->item, num_rows > MAX_STORAGE ? MAX_STORAGE : num_rows, 1);

Ensure p->item has num_rows empty spaces, not exceeding MAX_STORAGE. Thus:

Let's say MAX_STORAGE = 600

  1. if there are 5 items in storage, p->item will be a vector with 5 spaces (not 600)
  2. if there are 600 items in storage, p->item will be a vector with 600 spaces (ok)
  3. if there are 800 items in storage, p->item will be a vector with 600 spaces (hard limited)

In the new code:

VECTOR_ENSURE(p->item, MAX_STORAGE, 1);

Ensure p->item has MAX_STORAGE empty spaces. Thus:

Let's say MAX_STORAGE = 600

  1. if there are 5 items in storage, p->item will be a vector with 600 spaces (595 unused ones)
  2. if there are 600 items in storage, p->item will be a vector with 600 spaces (ok)
  3. if there are 800 items in storage, p->item will be a vector with 600 spaces (hard limited)

I think we don't really need to allocate those 595 unused spaces on case 1. But I agree that we should get everything and hard limit at MAX_STORAGE.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f2c4ffe

I see now, yeah, it shouldn't allocate more than needed.

I want to add though, if server owners decide to decrease MAX_STORAGE from 600 to, say, 500. There will be missing items for players that previously have 500+ item count, but only visually. I think this should be fine, though. Any thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While from an UX perspective it would be better that all the items (even the ones above the limit) are shown, but they can only be retrieved and adding is blocked until usage goes below the limit, I'm ok with hard limiting to MAX_STORAGE (and thus not displaying the extra items as you said) if that simplifies the code or the performance by hard limiting the size of some buffers.

Ideally, when over storage capacity, even if it's visually truncating the available items, it should still prevent the player from adding items though. For example, with MAX_STORAGE = 600, if player has 610 items, only the first 600 will be displayed. If one is removed (visually only 599 are present, but the total amount is actually 609), it would be best if adding any more was prevented. I understand this is an edge case, so if you feel it would complicate the logic too much, I'll concede.

Comment on lines -111 to -114
if (sd->storage.received == false) {
clif->message(sd->fd, msg_sd(sd, MSGTBL_STORAGE_NOT_LOADED)); // Storage has not been loaded yet.
return 1;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is removing the received check intended? from Haru message, he asked whether turning the message into assertion was intended (as an assertion doesn't inform the user why this happened).

But now, we would allow a not received storage to be "open".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I rewritten it as an assertion error but as Haru pointed out, it is no longer needed as the script and atcommands already errors out when called if storage is not loaded yet in intif_parse_account_storage

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that if script/atcommand is already checking, it makes sense to switch from a message to an assert, but I would still keep the assert, because this is the real "entry point" to storage functions and we are expecting it to always have a received storage. This guards us from future issue if someone calls storageopen in a new place and forgets to add the received check.

For example, let's say official adds a new button to the UI that opens the storage wherever you are, I could end up calling storageopen from this new clif function without checking for received because I did not know/forgot.

It would be ok to give a "weird experience" like not working and not giving info to user, and generating an error in console (due to assert) that we would fix later on with proper checks. But it would be a big issue if the unreceived storage simply opened with bad data and later on overwrote the real storage, or the items added at this time disappeared.

If I understood, from Haru message, he was just checking whether the change from user message to "console error" (with assert) was intended, not that it was no longer needed, but I may be wrong.

@MishimaHaruna may you share your thoughts on this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's as @guilherme-gm said. Please keep any assertions since they serve, if nothing else, as self-documentation of what a function expects, since it may be called by plugins or by future commands. I was only inquiring whether the removal of the user-facing message was intended

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-added assertion. 1bb030c

ShowError("intif_parse_account_storage: Multiple calls from the inter-server received.\n");
return;
}

storage_count = (payload_size/sizeof(struct item));

VECTOR_ENSURE(sd->storage.item, storage_count, 1);
VECTOR_ENSURE(stor->item, storage_count, 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memory manager reported a leak from this, I didn't check what is causing it (not sure if related to the missing VECTOR_CLEAR in unit.c

0001 : storage.c line 298 size 352 address 0x0x55976f3717e4
0002 : intif.c line 363 size 440 address 0x0x55977473f1fc

Copy link
Contributor Author

@jasonch35 jasonch35 Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added #3330 (comment), error now gone for me (I used builtin). Thank you!

VECTOR_PUSH(sd->storage.item, *item_data);
it = &VECTOR_LAST(sd->storage.item);
if (i == VECTOR_LENGTH(stor->item)) {
VECTOR_ENSURE(stor->item, 1, 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memory manager reported a leak from this, I didn't check what is causing it (not sure if related to the missing VECTOR_CLEAR in unit.c

0001 : storage.c line 298 size 352 address 0x0x55976f3717e4
0002 : intif.c line 363 size 440 address 0x0x55977473f1fc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

@vBrenth
Copy link

vBrenth commented Jan 7, 2025

Happy New Year @MishimaHaruna , @guilherme-gm Holidays are over, Please give us HERC user this feature. Thank you! its long-overdue.

- Limit rows to MAX_STORAGE, as additional rows are unnecessary.
- Loops based on capped number of rows so we avoid iterating empty items
@MishimaHaruna
Copy link
Member

I think we're almost good to go with this one, the only blocker on my side is the state validation in the storage functions (the missing/removed assertions).

Looking forward to merge it early in the February milestone if possible, so that it can sit on the master branch for a couple of weeks, giving anyone interested a little bit of time to test it in different scenarios before the Feb release at the end of the month.

@jasonch35
Copy link
Contributor Author

Tested and pushed new reviews. Please rereview @MishimaHaruna @guilherme-gm. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants