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

Improve structure error feedback #3884

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

Conversation

RecursivePineapple
Copy link
Contributor

This adds a mechanism for easily adding validation error messages for unique structure requirements. Many multis have specific requirements, but these requirements aren't communicated anywhere and there's no feedback for when the requirements aren't met. This PR allows multis to add error messages easily.

For now, it's only implemented on the cryo freezer and gt++ multis, but if this is accepted other multis can add their own validations easily.

This adds a mechanism for easily adding validation error messages for
unique structure requirements. Many multis have specific requirements,
but these requirements aren't communicated anywhere and there's no
feedback for when the requirements aren't met. This PR allows multis to
add error messages easily.

For now, it's only implemented on the cryo freezer and gt++ multis, but
if this is accepted other multis can add their own validations easily.
@RecursivePineapple RecursivePineapple added the enhancement Improve an existing mechanic. Please explain the change with a before/after comparison. label Feb 3, 2025
@RecursivePineapple RecursivePineapple requested a review from a team February 3, 2025 18:06
@RecursivePineapple RecursivePineapple marked this pull request as draft February 3, 2025 18:08
@RecursivePineapple
Copy link
Contributor Author

Drafting because I didn't know someone worked on something similar (just saw the merge conflicts). Let me see if this needs to be closed first.

@RecursivePineapple
Copy link
Contributor Author

Some examples:
2025-02-03_13 02 50
2025-02-03_13 02 45
2025-02-03_13 02 57
2025-02-03_13 03 00
2025-02-03_13 03 06

@RecursivePineapple RecursivePineapple marked this pull request as ready for review February 3, 2025 18:19
@RecursivePineapple
Copy link
Contributor Author

False alarm, I saw get/set DisplayErrorID and thought it was a similar mechanism but it looks like that's just for generic errors shared by all multis.

@YannickMG
Copy link
Contributor

YannickMG commented Feb 3, 2025

With this current implementation it looks like each implementation will need to define exactly how it wants to handle displaying a given error message, and that when said error message is displayed the data needed to explain the error may need to be reacquired if the StructureError isn't specific enough.

Letting each implementation decide how to display an error is great, it would make it simple for a given implementation to ditch the text display and use other UI elements to indicate the error instead.

The fact that the data relating to the error needs to be re-acquired is a little bit inconvenient though. It works fine for these simple cases, but I can see this feature being extended later to tell you, for instance, that an invalid block was found at position X, Y & Z during the structure check. In order to display a helpful error message we would effectively need to scan the structure again at render time. It might be helpful if these errors could contain the information needed to render them.

Now I see you've given this some thought since you labeled mStructureStatus a list of "unparameterized" structure errors. What was your reasoning for foregoing parameters and state? I recognize that using EnumSet requires foregoing parameters but holding information on why a structure check failed shouldn't be on any kind of hot path that would require it.

@Dream-Master Dream-Master requested a review from a team February 3, 2025 19:10
@RecursivePineapple
Copy link
Contributor Author

@YannickMG It's unparameterized mainly because of syncing. The structure check always runs on the server, while the text code always runs on the client. It's trivial to sync an EnumSet since you can easily convert it into a byte array, but trying to sync generic data would be way too complex when all that's needed in the multi is a few FakeSyncWidgets. Maybe you could do something with syncing a json object, but it would still be overkill.

A good example is the casing count. Multis may have one 'main' casing, or they could have several in more advanced structures. If my assumption turns out to be incorrect, and we only need to sync a couple of very simple variables, then we can add another layer or refactor this, but I don't want to make that assertion for now.

@Dream-Master Dream-Master added the 🚧 Testing on Zeta Do not merge yet, testing this PR on Zeta label Feb 3, 2025
Copy link
Contributor

@YannickMG YannickMG left a comment

Choose a reason for hiding this comment

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

image

Some structure errors are emitted despite not matching a structure requirement.

Perhaps you could move the code to add a specific StructureError to be part of the structure definition itself so that there can be no mismatch. Otherwise you'll need to find some other way of ensuring validateStructure doesn't need to be overridden by every multi with different requirements, unless of course that's the intent.

@YannickMG
Copy link
Contributor

@YannickMG It's unparameterized mainly because of syncing. The structure check always runs on the server, while the text code always runs on the client. It's trivial to sync an EnumSet since you can easily convert it into a byte array, but trying to sync generic data would be way too complex when all that's needed in the multi is a few FakeSyncWidgets. Maybe you could do something with syncing a json object, but it would still be overkill.

A good example is the casing count. Multis may have one 'main' casing, or they could have several in more advanced structures. If my assumption turns out to be incorrect, and we only need to sync a couple of very simple variables, then we can add another layer or refactor this, but I don't want to make that assertion for now.

That makes perfect sense. EnumSets are just fine in this case and it would be problematic to try to anticipate the needs of future parameterized errors in terms of network packets.

@RecursivePineapple
Copy link
Contributor Author

Otherwise you'll need to find some other way of ensuring validateStructure doesn't need to be overridden by every multi with different requirements, unless of course that's the intent.

Yes, that's the main goal. I figured it would be the simplest way to validate the structure. Most of the structure definition code is provided by structurelib, so I can't add to it easily. I have an in-progress project to help with this, but it wraps structurelib completely (it hasn't been drafted yet but I've used for several projects and it works very well - I want to iron out all the kinks first).

I could add some sort of List<Predicate<T extends MTEMultiBlockBase>> mStructureValidations mechanism to all multis, but I thought that would've been overkill when it could easily be replaced by a simple method call.

I hadn't expected steam multis to not require maintenance, I'll fix those. In cases like this, you would either change the superclass to require a boolean field for maintenance hatches, or you'd just not call super.validateStructure().

@YannickMG
Copy link
Contributor

This code doesn't appear to be foolproof since a machine may simply fail to add encountered hatches to mMufflerHatches.

        if (this.getPollutionPerSecond(null) == 0 && !mMufflerHatches.isEmpty()) {
            mStructureErrors.add(StructureError.UNNEEDED_MUFFLER);
        }

What's an example of a machine it does work for?

image

image

@RecursivePineapple
Copy link
Contributor Author

I'm not entirely sure what multis use it, it's just what checkHatches used to do so I kept it. Almost every gt++ multi emits pollution so it probably isn't that useful, but I didn't want to remove it without a good reason.

Copy link
Contributor

@YannickMG YannickMG left a comment

Choose a reason for hiding this comment

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

Fair enough. This will require additional work on several multis to get the most out of it, but it is a nice capability to have.

Copy link
Member

@miozune miozune left a comment

Choose a reason for hiding this comment

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

Regarding syncing parametrized errors, see IMachineMessage which is implemented by CheckRecipeResult and ShutDownReason. Making use of it would allow multis to display more detailed information about error.
While current code is great as a starting point, I believe we can use more refinement before going implementing all other multis.

Also, as pointed out by Yannick, having duplicated code for checking structure can easily lead to bugs. StructureLib code is not something we can hook into right now, but at least we can control other checks. I'd imagine something like this would be great: Nvm

```java // Specific to multi List validateStructure() { List errors = super.validateStructure(); reset(); if (!checkPiece()) { // We can't access StructureLib internals, but at least we know something failed with checks errors.add(StructureError.incorrectStructure); } if (mCasing < 10) { // We know exactly what went wrong errors.add(StructureError.tooFewCasings(mCasing, 10)); } return errors; }

// MultiblockBase
boolean checkStructure() {
List errors = validateStructure();
this.structureErrors = errors;
this.mMachine = errors.isEmpty();
return mMachine;
}

</details>

@RecursivePineapple
Copy link
Contributor Author

RecursivePineapple commented Feb 7, 2025

I made the mStructureErrors field private so that we can refactor the system easier if needed and added it to the parameters of validateStructure and getStructureErrors. I also added an NBT parameter so that multis can add arbitrary data (it's synced properly, though it has a bit of latency).

I thought about adding some sort of interface system to hide some implementation details, but it would introduce too much boilerplate for not much gain (a bit easier to refactor and namespacing for the nbt data, which won't be a problem). There aren't any limitations to the current system that I know of (though I will admit it's not the prettiest). I think the simplicity makes up for its lack of prettiness (I'm not a fan of excessive boilerplate).

Something like this:

interface IServerErrorStorage {
  void addError(StructureError error);
  
  void addError(StructureError error, NBTTagCompound data);
}

interface IClientErrorStorage {
  boolean hasError(StructureError error);

  NBTTagCompound getErrorData(StructureError error);
}

Copy link
Member

@miozune miozune left a comment

Choose a reason for hiding this comment

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

I'm still not entirely convinced by NBT syncing v.s. IMachineMessage approach, but it's true that NBT can carry flexible information without requiring distinct class, which would the case for structure errors. I'd like to approve the idea now.

Here are some comments need addressing before merge. Sorry for the late review!

@@ -173,6 +178,15 @@ public abstract class MTEMultiBlockBase extends MetaTileEntity
private static final int CHECK_INTERVAL = 100; // How often should we check for a new recipe on an idle machine?
private final int randomTickOffset = (int) (Math.random() * CHECK_INTERVAL + 1);

/** A list of unparameterized structure errors. */
private EnumSet<StructureError> mStructureErrors = EnumSet.noneOf(StructureError.class);
Copy link
Member

Choose a reason for hiding this comment

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

We should avoid introducing m prefixes for new members, so structureErrors

* Any implementation-defined error data.
* Private so that multis have to use the parameters (to make it easier to refactor if needed).
*/
private NBTTagCompound mStructureErrorData = new NBTTagCompound();
Copy link
Member

Choose a reason for hiding this comment

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

Same here

}
mStructureChanged = false;
return mMachine;
}

protected void validateStructureImpl() {
Copy link
Member

Choose a reason for hiding this comment

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

Suffix Impl refers to a method which child classes override. The name should be swapped so that child classes will override validateStructureImpl.

Also, since the intention is to not let children to override this method, it should be marked as final. Keeping protected is ok so that it can be called from anywhere.

* @param data Generic data blob generated by {@link #validateStructure}.
* @param lines Add text to this. These lines will be shown in the controller GUI.
*/
@SideOnly(Side.CLIENT)
Copy link
Member

Choose a reason for hiding this comment

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

SideOnly usages should be restricted where it would cause crash if not present. It can be removed.

* Scans {@code errors}, {@code data}, or other fields as needed and emits localized structure error messages.
* The {@code errors} and {@code data} params are synced already, but any other fields must be manually synced.
* Note that the parameters may not be in sync due to network latency (they are synced separately). You shouldn't
* rely on a field being in {@code data} if an error is present.
Copy link
Member

Choose a reason for hiding this comment

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

You shouldn't rely on a field being in {@code data} if an error is present.
Can this be clarified a bit more? Especially whether "error" refers to errors argument or general word is unclear.

}
}

return out;
Copy link
Member

Choose a reason for hiding this comment

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

I believe it's worth creating dedicated class extending FakeSyncWidget, since how StructureError gets synced has nothing to do with multiblock.

super(aName);
mFuelStack = FluidUtils.getFluidStack("cryotheum", 1);
CASING_TEXTURE_ID = TAE.getIndexFromPage(2, 10);
protected MTEIndustrialVacuumFreezer(MTEIndustrialVacuumFreezer prototype) {
Copy link
Member

Choose a reason for hiding this comment

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

What's the intention behind the change to the constructor? (besides mFuelStack and CASING_TEXTURE_ID removal) I mean it works, but also inconsistent with all other MTEs. (Yes MTE system is annoying, I'm just not sure if we want to mix two styles for newMetaEntity implementation.)

* Any implementation-defined error data.
* Private so that multis have to use the parameters (to make it easier to refactor if needed).
*/
private NBTTagCompound mStructureErrorData = new NBTTagCompound();
Copy link
Member

Choose a reason for hiding this comment

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

Can this have better naming? Having structureErrors and structureErrorData looks confusing to me. I'd suggest structureErrorContext. And data argument in validateStructure and getStructureErrors can be also renamed to context if you like this idea.

* @param lines Add text to this. These lines will be shown in the controller GUI.
*/
@SideOnly(Side.CLIENT)
protected void getStructureErrors(Collection<StructureError> errors, NBTTagCompound data, List<String> lines) {
Copy link
Member

@miozune miozune Feb 11, 2025

Choose a reason for hiding this comment

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

"Get" sounds too generic when it's called solely on client, gathers error strings rather than StructureErrors, and returns nothing. I'd suggest gatherStructureErrorMessages, addStructureErrorMessages or buildStructureErrorMessages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🚧 Testing on Zeta Do not merge yet, testing this PR on Zeta enhancement Improve an existing mechanic. Please explain the change with a before/after comparison.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants