#Transfer rework (IItemHandler, IFluidHandler, IEnergyStorage)
1 messages · Page 7 of 1
Oh. I have no idea what they will do, but when it comes to multiloader things this may make the logic similar, but require this implementation to use
It will at least make the concepts similar I believe, but I honestly dont know given I never really use fabric
Given I have never looked at architectury nor its API… your guess is probably better than mine
me when i didn't double check my message and pinged the wrong person
I'm sorry 😭
I have used architectury in the past, but mostly only for loader infra, not content stuff. It's possible that since the Fabric and Neo api's align a lot more closely now, a common abstraction in architectury becomes easier, but that's something the architectury devs will have to figure out
i was unaware that architectury api even handled cross platform transfer
last i checked it explicitly didnt. one of the devs literally told people to use the xplat library I wrote designed for that purpose 
okay i just checked, it doesnt
I'm sure a solution can be made with it, but it very much not a goal of mine 
I'll probably continue to support common storage lib in the future, so thatd probably be an option 😄
Like, <T> insert(int index, SomeResource resource, int amount, T transaction) may be a starting point but bleh
since the transaction system here is fully locked down
Ok I will merge the transactions slice in a bit. For the resources slice I'm still waiting for ResourceStack to be removed before having another look
milestone 1™
yeah thats what i was wondering if its now going to implement it
here's an easy follow-up PR: https://github.com/neoforged/NeoForge/pull/2552
Sorry for butting in, but I was wondering about the annotated type parameter @Nullable T in the SnapshotJournal.
How can a type parameter be null, what's the meaning of this annotation?
And a follow-up question, is it sound to cast the sentinel object to a T and put it into the internal list?
Couldn't this lead to problems when retrieving values from that list and assuming they are of type T?
Ideally it should mean T extends @Nullable Object and allow the bound to take nullable types. In practice intellij seems to not care either way
The list is internal. It needs a bit of care with the sentinel value indeed, but that won't leak to the outside
Ah, so all usages of T inherit the @Nullable annotation 👌
Ideally it's only nullable if the type bound is (e.g. SnapshotJournal<@Nullable Something>)
The new api can resolve the problem about proxy cap ?
proxy a proxy cap and we got a loop
This doesn't change the cap API, however this problem already has a solution anyway
so how to resolve this problem
You have to check
@silent root do you already know when you'll get to that? Can't start the next slice while resources are still under review
So:
- What is "non-trivial" about the current implementation of the
ResourceStack - Have you even looked at it since the changes?
- What do you mean remove it? You were wanting it "trivialized" to a record, which is partially a problem with creating arbitrary empty instances which the current system prevents and provides the singular empty instance for each resource. The user side is a single method/field.
IMHO I don't think making it the EMPTY be a singleton is necessary
What?
I would just allow arbitrary empty instances
Not ideal when we take into account that people are likely to use this as each index. So say a chest with 108 slots, would you want them to have 108 of the exact same instance repeated when it could have been known? (Then expand that on chests scattered throug the world)
It DEFINITELY makes things easier when we can do ResourceStack.of() and shrink to get a singular instance when they would have been otherwise empty. Especially with how trivial it is to do
I doubt that last statement, it is not easy to do in a generic way
And the code you have for it now
Is for sure really difficult to track and maintain
It is REALLY trivial.

And solves all of the problems that were listed to me at the start of this
No worries
Your old code was actually quite horrible
Yeah it wasn't grand
The self referencing generic is really useful, but adds too much clutter and becomes too unwieldy
This was a hybrid approach of what @mental raven originally proposed without having a user need to implement their own
This also means that IStackFactory for resource stacks are no longer needed for the codec since now we can guarantee empty is returned correctly, which simplifies its use (along with the other slices have been helped). ResourceStack.codec(ItemResource.CODEC) is rather elegant
Yeah
@flat root review it again, though do your best to not be in the mind set of "We need to axe this" and actually see what problems remain with it.
I still really dislike ResourceStack overall. I feel the few very limited cases its used in the full PR could be trivial records of IResource & amount. Anyone implementing their own resources is sure to want their own mutable variant to compliment vanilla. I don't think it will be overly useful, I think it should be removed, and re-added in the future when there is a lot more real-world load on the api, and we can properly evaluate if it will be useful for modders.
Us having the immutable ResourceStack, which is incredibly useful for generic resource handlers to be able to simplify their logic for cross mod support, doesn't prevent someone from having a mutable (or similar) stack type in their implementations should they desire it. I was alright removing the MutableResourceStack I had in the past, but I am not so keen on removing this.
This phrase "trivial record" is really you want a tuple, and unfortunately, I still would prefer we reduce allocations where we so clearly and simply can.
No it does not prevent anyone from making their own mutable variant. However, the resource helpers can simply return a trivial record (yes a pair), it does not make these helpers any more complicated or difficult to implement/use.
https://github.com/neoforged/NeoForge/pull/2415#discussion_r2203511525 There are 5 maintainers (including myself) which would like to see this change. I will not be reviewing till its changed.
Have you looked at the newer code?
Yes, I have. Yes we cleaned up how empty instances are plumbed around. But I still feel that we don't need it, and would rather see it go and added back in the future once we have prod data and feedback on the api's usage.
I dont understand why we are in favor of having 1000s of copies of empty instqnces though
Between mods of different resource types? Now every mod needs to handle something we could provide them?
How many mods do you estimate that have custom resource types?
I would say less than 20 types
excluding energy
How many of these resource types would already have a mutable data structure for their resource?
I would estimate basically all of them.
Given the immutable structure comes with increased object throughput, we should not be encouraging these mods downgrade from a mutable structure to an immutable structure, especially with something as frequent as resource transfers.
As i have said, I want to see real-world data on where it makse sense to add it as an api alltogether, opposed to it being a trivial pair for ResourceUtils.
Again... I had a mutable one and an immutable one. I was told to remove the mutable
Who asked for removal
It was on the original pr so would have to check
The resource stack though is ised in the handler for Contents and the ResourceStackListHandler (though my loval copy as the newer changes)
Having it trivialized storing values for arbitrary resources
I don't think storing entries in handlers via immutable data structures is a good idea. These should be purpose built mutable structures for performance.
I had said this too, which is why I had the MutableResourceStack but in the cases of a component ResourceStack was effective and necessary
I don't think there is a good solution here. Mutable ResourceStack would have too much overlap with vanilla, and I don't like that. The immutable ResourceStack promotes incredibly bad object usage, and memory awareness. I, still, would rather it go away and be replaced with purpose-built wrappers.
So, if there "is no good solution" why not have this as a entry level point for users to have a simple time for integration, but if they need something more advanced or purposeful they can still use that? As I'm not sure the proposed "everyone do it themselves" approach will help performance at all. Im all for improving what we can, but axing it doesnt help anyone and makes the non built in resources more tedious to support per modder.
The complaint was the plumbing. We pretty much solved that. With the newer code and streamlined modder use
This 'entry level' solution, is the worst possible solution. We don't need more immutable data structures in hot paths.
I think i have provided more than enough technical arguments for my viewpoint
"Worst" ok even it it was, but really disagree with that, your proposed is to not have any. Which Id prefer alternatives or ideas rather than "I have spoken"
What were they?
Removing for a trivial record isnt a solution it is the same thing with now new allocations for empty??
Purpose built wrappers required for the rework. I don't think a generic ResourceStack system is in-scope for the rework
I'm not impressed with how difficult this rework is to get through. This has been an uphill battle from day one to get moving. If 5 maintainers asking for changes is not good enough, why should we continue?
A trivial record would have a single allocation per resource transfer, the result of ResourceUtils operations, not tens of allocations per transfer.
Im going to be honest, I dont like being a burden, but it is incredibly scary to me that when code is submitted for review, that the only course of actions we can take are to get petted or roll over on demand, but we arent allowed to try to come up with solutions or have discussions. They are all just demands and expected to be followed.
Changing code is one thing, wiping it is not as useful.
I think we have had sufficient discussion on this topic. You have presented arguments, and I'm personally still not convinced. Thats just how reviews go.
I don't expect submitters to rollover on demand, I expect to have a technical convosation, and we have, but personally for me, the time for discussion is over, i am unconvinced.
Just to point out, without an immutable struct, youd need to make a new allocation anyway for transfers due to the snapshot journal.
We have about 7 slices to go through still. If you're not making the requested changes it will take months. This rework is particularly important to get right, so you'll get more reviews than for an average PR, and you're expected to listen to what maintainers tell you. If you keep arguing we can get someone else to do the changes.
Just a slight ask what version will this be out for ?
Whenever it's done
there is no ETA yet, and there probably won't be one until it's done, or close to done
Frankly, I'm a fan of ResourceStack. Almost every scenario where you're dealing with some amount of something is in some way related to transfer, and the whole point of this rework is partly to make a general interface, which IMHO rules out any "purpose-built structure". Furthermore, I see an incredible synergy with the fluid rework I'm doing which would completely eliminate the need for FluidStack, leaving only ItemStack for compatibility with vanilla.
This rework is particularly important to get right
Speaking from personal experience, this will never happen. We shouldn't be chasing impossible perfection. I believe it has been brought up a few times now already, where we should wait for real-world data before making a decision. So why don't we do just that, and allow these, frankly, trivial differences slide until we have the data which actually suggests whether people are using or not using something?
It seems that we are already mostly in agreement with the general shape of the API, but currently are grinding gears on comparatively trivial implementation details, which will almost certainly change in the future anyway
Why not embrace that we are also human and make mistakes? We can simply see whether people in the real world, in a production environment with multiple mods, make mistakes or have feedback, and then make further changes in response. This has happened many times, because it's a simple fact that the ways we think the API is going to be used are not, in fact, how people actually end up using it.
The maintenance burden is irrelevant - ultimately we are going to need to maintain what people actually want to use, or people will simply move to platforms where the maintainers do end up maintaining what people want to use
No, but I also won’t be able to until next week. But if it truly is cleaner than it was previously, we will see what side of the fence I end up on

Update: I'm redacting most of what I said earlier, after a (rather lengthy) internal discussion. I'm on board with simplifying ResourceStack to a resource/count tuple/record (potentially with a sentinel value for empty, pending future review)
I was looking over it all again. One reason I finally agreed to remove MutableResourceStack: when ever the value is changed in a transaction, we need to take a snapshot (journal.updateSnapshot(transaction), that allocates if it is mutable copying for the snapshot, and if immutable create an instance on setting the new value. Meaning the immutable versions will be the exact same costs as mutable, gaining you nothing unlike before where we could just do simulations. Mutable structures WOULD have been fine, but transactions providing us a great deal of improvements did have a cost of requiring rollbacks.
Can we lay out all of the current problems we have with it? What I have right now:
Immutable structures require allocation per transferWe have to regardless of structure chosen so that isn't really valid anymore.PlumbingIt was simplified for the user to be a field and method
Then following that, what example of a "purposeful built" structure can you give? As so far, it seems that what we have right now allows TRIVIAL generic containers to be made, but without it, it becomes very much a burden on the user to reinvent the wheel for each resource.
it basically is that already? what part of it needs to be simplified?
Trivial record, no helpers
but why?
Maintainers decided so
Isn't that enough of a reason?
See: the previous discussions when maintainers were not convinced of the points in favor of the current design
To be clear: we decided democratically, with an internal maintainer vote, like we do with mostly everything
It is easy to add back helpers and such later. It is hard to remove bloat without causing breaking change. The PR should be minimal viable product basically. Simplest and cleanest form. Then see if later, if people need more or not from it.
Multiple maintainers looked at the current ReaourceStack class and basically all saw it as bloat. Therefore they wanted it to be simplified to a record of just the resource and int and nothing more.
It does shift a lot of the burden to the user though as now there are 4 different "valid" infinite instances of an empty resource stack vs the 1 we allowed and provided if it were empty in any case. I'm testing around with making solutions for just a "trivial record", but it definitely isn't great. Regardless, I'm waiting until Pup has at least had time, and we can discuss it more then.
Please refactor it to a record, now
We have discussed this to death
Over the last 2 weeks
You do not need to wait for pup to make this change
Basically, if this transfer work is to get moving, Soaryn, please perform the changes that maintainers ask for. Pushing back on every requested change is going to deadlock this PR. Right now, that pushback is becoming a massive pain point for us maintainers and we need you to stop and please listen to us
To further clarify this message.
I reckon my answer was quite direct. Given that this had already been discussed already just before the question was asked (discussion beginning, question), I felt the question to be a bit disingenuous. I recognize this as a mistake on my part.
To properly answer the question though, see Telepathic's answer just above, but also previous statements by Maintainers (covers 1, covers 2, Monica, pup (on GitHub)).
Not happy about being forced to do this, but looked at it briefly on my phone and I am indifferent. The as(stackfactory) seems massively overkill and definitely should be removed, but other than that it seems to be a simple record (with validation) currently, plus codecs. Not really sure the overriding to string is necessary, but 
That was what I just pushed not what we had to be clear
the as was either something to keep or moved to utility, but having to type out what it all was before each time I wanted to use was not good UX which is why I added the as
This was pre changing to record https://github.com/neoforged/NeoForge/pull/2415/files/06edfe547153c7e4cdc2f1a0cf53583bd946c091
It shows me your last commit was yesterday, so maybe GitHub is trolling me
May have been just as soon as I pushed then 
(just now)
But that link should be all changes up until the record
Bunch of helped that are overkill for now. Indifferent on the single instance thing
Though if it is a performance bottleneck we could always just intern it down the line similar to how resource keys or whatever are. But only bother interning the empty variant
@mental raven since you want to get rid of the immutable and all other utilities related to ResourceStack, may I at least add a way to "deconstruct" the "built in" stacks (item/fluid) to their relevant resource types with an interface?
public interface IStack<T extends IResource> {
T resource();
int amount();
}
On ItemStack it would just have the extension extend the interface with IStack<ItemResource>, with the FluidStack, it would just implement it directly IStack<FluidResource>.
That would be a question that other maintainers have to weigh in on. Personally, no
no, out of scope
How is it out of scope?
it is not needed in the first version of the transfer rework; the transfer primitives operate on the resources, stacks are really a secondary concern
I think it's feature creep, we should focuss on finishing what we have and getting it in. We can add more stuff later
The commit names going into the PR is quite passive aggressive. Not very friendly…
Hey @silent root, we have decided with a near-unanimous vote that the NeoForge maintainers will be taking over the rest of the transfer refactor. We all want this rework to happen sooner rather than later, however given its scope and impact we also want to have it properly reviewed. The way you have reacted to requests for changes has been inappropriate, delaying the rework and creating increasing amounts of friction. This cannot go on for the remaining slices, so us maintainers have decided that we will be making the changes we want to see directly.
Concretely, this means that maintainers will be making the final changes to the currently open resources slice PR, and take care of the subsequent slices via maintainer-opened PRs, starting from the slice commits in Adrian's repo. Each slice will be properly reviewed by the maintainer team as usual. You're, as always, still free to discuss the PRs both on github and in this thread, and so is anyone else!
With this, we also hope to be able to deliver the transfer rework sooner and smoother overall, ideally in the upcoming Minecraft release.
so, I am finishing up the resource slice. I want to rename the ItemResource#is(ItemStack) method since it's confusing: it checks both the item and the components while the other is methods only check the item
we could do isSameItemSameComponents but that's a bit long
I wonder if matches would make sense? Yes ItemStack#matches also checks the size (and is static), but item resources are basically countless stacks
so it might be semi evident that there isn't an amount for it to care about?
matches is what I used in Fabric so I would definitely be in favor 😛
I was hesitant because there is a matches method for stacks already, but it's static and it's quite obvious that we don't check the count, as you said
so let's go for matches
regarding this, I suppose we could check for the class and do a cast?
I think the standard pattern is to getClass() != obj.getClass() return false; otherwise cast. Should be the same here?
public boolean equals(Object obj) {
if (this == obj) return true;
if (this.getClass() != obj.getClass()) return false;
ItemResource other = (ItemResource) obj;
return ItemStack.isSameItemSameComponents(this.innerStack, other.innerStack);
}
like this?
looks about right to me at least
Need to check null then too
this == obj will fail if obj is null
(and if this is null then you can't call equals on it anyway)
ah right, forgot the null check
I was solidly confused for a moment and was quickly writing one to check myself 
interestingly intellij doesn't complain about the missing @nullable
@noble latch and I had a brief discussion at one point between the speeds of the instanceof vs the class based check, and in theory for this case instance of was technically faster since it was doing more in one go iirc. It is negligable, but in readability, the class check one is more obnoxious typically
generally getClass is recommended to avoid symmetry problems with subclasses, but for a final class it doesn't matter
I already changed it to getClass here
@cunning mulch your comments have been addressed I believe, so we're just waiting for @hidden geode
For final classes I usually do instanceof because it lets me inline
return this == obj || (obj instanceof final ItemResource that && ItemStack.isSameItemSameComponents(this.innerStack, that.innerStack));
Though this is something I wouldn't recommend lol
Long lines
instanceof on final classes is implemented as == getClass with null guards in the jit
ah ok so no perf difference either way then I suppose
(usually instanceof is somewhat slow)
Atleast when i looked into it in j8, i highly doubt that has changed
it's a pretty obvious optimization, and it seems like it could make quite a difference
Generally i prefer manual getClass comparisons for equals though, as its what intellij uses when you tell it no hierarchy
Correct, particularly on interfaces from memory
Or maybe i'm thinking of invikeinterface vs invokevirtual
I like this just because it's what vanilla uses and is immediately obvious to anyone
Also autocomplete is a thing so long names are fine imo if they are excessively clear for everyone
my desktop is extended across two monitors so I can just make my IDE really wide if I need to
my monitors are different size, different resolution and different pixel density, so if I did that it would look weird as fuck
One monitor is a curved 4K monitor and the other is a CRT
I would definitely prefer instanceof X to x.getClass() == ThisClass.class
transfer rework tomorrow
Do we need a method other than equals?
It'd likely be better to have them be separate methods to ensure intent when checking.
I don't think that's confusing at all, as long as it's documented
No reason to convert a stack to a resource just to compare with equals
@hidden geode reminder: the resources slice is waiting for your review
Final pass started
One issue remaining, this whole "defaultInstance" mechanic, seems to not really be well designed, and IMHO completely overkill. The overhead of returning a new ItemResource or FluidResource when the DCP is empty seems to be not worth it, especially when considering the dance that we need to do with Item and Fluid to make that work properly.
I agree, they don't really seem worth it.
what don't you like about it?
The entire system
It seems over-engineered for near to no gain in performance
And it causes a maintenance headache
iirc it's to reduce gc churn with all the allocations
IMHO we can address that later as it is an implementation detail
I would like to rip out everything that is not actually needed, and then have reality test it
If it is a problem with the GC Churn, which I doubt, since ItemStack does not have that logic, and its GC Churn is under control as well
Actually, the claim here is that most resource transfer happens without data components. And so it makes sense to cache the resources with an empty patch
Thus reducing the amount of allocations by quite a lot (e.g. getResource on a vanilla container would otherwise allocate for every single call)
It might be a little bit cleaner with a mixin to hide the method though
The method being visible isn't great
Primarily because of auto completion
TBH one could do a separate class that accesses package-visible method on ItemResource to construct the Item instances
That would be better. I'd recommend keeping the allocation cache. Most resources are going to be moved around patchless by consumers, so where we can save burden is ideal. Especially given the minimum requirement of the thing. It'd be different if it were spread across several classes, but it is 2-3
IMHO We can add this implementation details later, especially when using package-private classes if it turns out to be needed
But right now from bare testing I don't see a need
The equals is also performantly better if we leave it in
I think the optimization is very valuable, otherwise scanning a double chest will potentially allocate 54 objects when that could have been avoided
But we can indeed remove it for now and figure out the best way to add it later if needed
That would only apply to items with default components no? As soon as anything derives from the norm it falls through, which IMHO happens quite a lot, especially in modern environments no?
I would very much like to see that yeah
These are details which we can handle later when we have more data on the usage and its costs
Only if something actively changes its nbt
Most common resources that a player might obtain usually don't have a patch. But of course a chest full of mob drops will have patches
And even thrn most mobdrops dont have patches
Only weaps and armors from that pool typically.
Agreed (with the caveat of without changed data components). Especially for say fluids as I don’t know if any mods actually even use data components on the fluid stacks
It doesn’t for fluids
And for items meh, deviates some, but there are still lots of blocks that get moved around that players broke in the world
Yeah I think the "with default components" is just meant to be a generalization to mean "empty patch"
It isn’t just machines or stuff that are being moved
Most items and fluids are their "default instance", so caching the allocation is certainly worthwhile
The way it's currently wired is certainly not great though
It adds a public method to Item and a public method to ItemResource - both internal
That part is fair, but we still should have it, even if we just change how it is wired slightly.
I wonder if we could somehow do away with the one on the resource by instead just making it so that there is some way that while currently initializing it then defaults to a new instance and otherwise using the existing default
Unsure, and also need to go for a bit so can’t bounce ideas back and forth for a bit. I will probably be back in an hour or two
That could work, but isn’t exactly what I meant. I sort of meant some custom lazy impl that stores if it has been initialized (and if it has started initializing) and then if it is initializing it is the one case it is allowed to return null. But then it calls Resource of, which if it gets null for the default resource instead returns a new instance which then the default instance that called it is able to cache
It would be a larger patch (and potentially something that should be in a helper class instead), but I think it would work and also allow skipping adding a map lookup
And then there is only a single method marked as internal instead of two
Ah yeah that can work. A bit meh though. Cause you'd probably need a setter then on the Item class
Imho it is still a premature optimization. I think it should be removed from this PR and reconsidered later with more data and a cleaner implementation
The vast majority of transfers involve default item instances, so this has big impact. However, it can be done in a separate PR to remove the methods we dont want to be so visible now
I don't think however that new transfer will go live without this
That is all I am asking
That is fine we can collect some data in a test environment and check it out.
Are you gonna do this work?
A guaranteed allocation for every single slot read for vanilla containers doesn't look good at all, so if nobody is gonna do the performance benchmarking (and GC benchmarking is really not fun), we should implement the optimization
We can discuss the best strategy to implement it (e.g. map lookup vs field in Item) but I think that removing it is not the right call
Well three options:
- merge it as-is, hide the methods in a follow up PR
- remove it from this PR, re-add in a followup PR with better hidden impl details
- remove it altogether
I have no problem with either 1) or 2)
But I think we should hide the impl better
Either 1 or 2 is fine by me as well
1 or 2 is fine by me
i'm not sold on the map, but we may be able to do some recursive init magic
I think it is important to have, and we can just hide it better in the future. It isn't worth the risk of it somehow being missed/forgotten to be added when we aren't looking specifically at the resources and are instead looking at the transfer stuff
so I say 1 makes most sense
given it realistically is directly resource related which is what this slice is
nah, it definitely would be a bit meh, but I more was thinking the following which doesn't need a setter:
private boolean cacheInitializationStarted;
@Nullable
private ItemResource defaultResource;
@Nullable
@Internal
public ItemResource getDefaultResource() {
if (!cacheInitializationStarted&& defaultResource == null) {
cacheInitializationStarted = true;
//ItemResource#of will end up calling getDefaultResource, but we won't enter this branch because of the boolean set
// and instead will just return null
defaultResource = ItemResource.of(this);
}
return defaultResource;
}
//In ItemResource:
public static ItemResource of(Item item, DataComponentPatch patch) {
if (patch.isEmpty()) {
ItemResource resource = item.getDefaultResource();
//Handle if the resource is null because we are being called by the item when it is instantiating the default resource cache
return resource == null ? new ItemResource(new ItemStack(item)) : resource;
}
//existing code for making the stack with the proper patch
}
I am for 2)
I would avoid a map in comparison to immediate access. That lookup is much more expensive than just "get default of me" even if "immediate" goes through a lazy init
Is there a specific reason we are not eagerly initializing that field?
There is an event that is called rather late
that modifies the default components for items
Can't remember if that event is expected to be called on world load or fine on main menu
Does that matter if the ItemResource is just holding an ItemStack with an empty patch?
Yes, as empty patch isn't known until AFTER that event
because it might make the default components not be empty
so then the item stack will be a patched component map with defaults or with empty
I guess
Hm? Shouldn't the patch always be against the item default components?
so, empty patch always means == item default stack
ItemStack creates patched component map
That event can modify the default to be a new default set
Okay it does store a reference to the prototype
that is the actual problem
So yeah, ok. In that case we should document that at the lazy initialization site.
As for the function to create the instance, we can do that in a package-private Internal class. I have no problem with an internal method being accessible, just with it being in the very visible auto completion list for ItemVariant/FluidVariant
I think this works reasonably, but i'm still not a fan of having getDefaultResource as a public method on Item
one thing we might be able to do is cache the ItemResource directly inside the ItemStack
we can probably keep it quite cheap by playing some tricks with the copyOnWrite field of PatchedDataComponentMap
so yeah there's a number of ways to do it; for now I'll postpone (i.e. go for 2)
Add a todo in item resource though (ideally with some standardized tag for us to search for) of things to address before final merge into master
I will just open an issue instead for more high-level things
we might also choose to release without this, but we should keep it in a list of "follow-up things"
Ok removed. @hidden geode quick merge?
Approved
Which slice(s) is next?
first step: yeeting transfer characteristics
hmm, I actually wanted to have the branch on the NeoForge repo, not mine; oops
I actually really like the concept of transfer characteristics honestly
It lets us optimise common scenarios (e.g. "infinite" or "voiding" containers) without actually knowing the exact implementation
the supportsInsertion/Extraction is honestly kinda ugly and hard to extend by comparison
and also has the problem of people treating it as a ground truth rather than a hint
(the naming implies that if you return false from e.g. supportsInsertion nobody will try to insert to you, which isn't true)
Bigger thing imo, would be if we bothered to use it to simplify our templates to not have to have so many unique duplicate classes for what is effectively a switch statement
Though I haven’t looked at the system yet so still unsure if it actually makes sense/is implemented decently
What about, for example, if I have some sort of "condenser" which converts items into some energy/currency, but doesn't have a way to get the item back out?
That would be a "voiding" container, but it wouldn't use any of the existing templates
And subclassing the "voiding" handler also doesn't sound right to me
I can’t tell if this is targeted or not given one of the two (semi notable) mods I maintain has a block that is basically that
Nope, I was thinking of something like the AWESOME Sink from Satisfactory 😅

But also mods like AE2 and, yeah, equivalent exchange, have similar concepts
We already had a lengthy internal discussion on the characteristics, that was the tipping point for the slicing
Ehh, more so from the commit being seemingly out of nowhere
Did we? My understanding that the "discussion" we had was that, yes, the commit was seemingly random
I am currently undecided
These containers are so trivial that there's nothing to optimize
Consumers*
Right
If a consumer is making e.g. a item pipe network, they could automatically lower the priority of handlers which are "voiding"
I do agree that we should slim down the number of "templates"/"containers" we have, but the concept of characteristics presents a really clean way to categorise containers and handlers without relying on the exact implementation, and is WAY more expandible in the future than extra methods on the interface
This is all quite theoretical talk that doesn't hold up in practice I'm afraid
And, I also think it is likely to prevent misuse from people misunderstanding "supportsInsertion" and "supportsExtraction"
Another thing to discuss is whether to throw in case of negative amounts. Personally I would support that
I'm not sure how you can argue that when, as far as I know, there's no existing system that is even remotely similar?
(Within the context of Minecraft, that is; spliterators of course are where the concept originated)
Meh tbh I still feel like the supports insertion/extraction in general is weird/a preoptimization. Like sure I know the existing system supports it, but I would honestly be more in favor of holding off on having them (or characteristics) in the new system (at least for this slice)
Then again I never have really used them 
They exist in part as a parallel to canReceive/canExtract for energy
I know
The usefulness is arguably very limited
That's fine by me; I think characteristics are a better solution but I don't think they're necessary for an "initial rework" PR
I think the only people who would be interested in characteristics/supportsX/canX are people implementing resource routing/handling networks (e.g. Mek pipes, AE2 buses, Xycraft pipes)
I mean I think they are a better solution. But I am not sure about for the initial PR. And I think one way to leave it as more open to them might also just be to not have the supports thing off the bat even if the existing system has it. Given how different people implement the methods currently, it isn’t really a loss. I don’t consume those results at all currently (and my returned results for mek’s stuff is meh as the place that wants them is way too many wrappers deep for me to want to pass through some sort of hack to be able to provide a “perfect” return
In some ways. If you know something can never accept anything and you can only output. You can just cache it like it doesn’t provide a cap. But still… unsure
Yeah; for a built network it's certainly convenient being able to skip any handlers that you know ahead of time are likely to never accept an item for insert
It also means that you can also e.g. provide a handler that can neither insert nor extract for "visual" connection, which is necessary for some mods/pipe networks/etc
Apart from questioning the usefulness of the characteristics, I have exactly one thing to say about them (which I had also mentioned in the previous discussion on this topic): bit masks are amazing for implementation details but as part of the API they are garbage.
Case-in-point: I was playing with the Class File API earlier and building a method with it requires immediately specifying at the very least whether the method is static or not. Since the last parameter of ClassBuilder#withMethod() is a builder consumer, the flags cannot be passed as a vararg. Instead they opted to use an int for a mask with several ways to populate it and none of them referenced/documented properly.
Yeah; the design was to my understanding lifted mostly entirely from Spliterators
I'm going to be honest, transfer characteristics seem kinda obnoxious to me, and even the supportsExtraction/supportsInsertion I... don't really understand the point of?
Like, I guess so that someone can avoid interactions that they know will fail (aka, avoiding trying to extract if it's known to be unsupported)? With that in mind, the supportsExtraction/insertion seems fine to me even if a bit early (and I mean, is it even cacheable? Is it something a resourcehandler is allowed to change?), but the characterestics were... something that seemed like it'd be obnoxious to deal with, to me. Especially the "voiding"/"infinite" ones or the like -- they're just too blurry of a definition (I mean, consider vanilla, even -- is the vanilla composter "voiding"? Must it be destroyed without side effect to be voiding? And at that point, what counts as "destroyed" anyhow? Same with "infinite" or the other categories -- does "infinite" mean I can always extract? What if I have a thing that conditionally provides as much as you want to take from it? Etc. it's all just kind of ugly and I feel like categories, should they exist, need much more explicit requirements than the characteristics had), so I think tossing that is probably a good idea for now
Ahh, I see my question on caching is answered in the javadoc, cool. Well, supportsInsertion/supportsExtraction seems fine to me then
I'd actually like to see characteristics as an interface with some sane "basic defaults" supplied, calling it metadata
Something like
interface ITransferCharacteristics {
boolean canInsert(): true;
boolean canExtract(): true;
default boolean transformsInputs(): false;
default boolean isVoiding(): false;
}
class CommonTransferCharacteristics {
ITransferCharacteristics STORAGE = (true, true, false, false);
ITC SUPPLIER = (false, true, false, true);
ITC TRASH_CAN = (true, false, true, true);
// etc
}
Where our APIs offer one of these up as part of its contract, and we can control characteristics/metadata separately from implementation
Usage being something like:
// find everywhere on the network we can insert stuff safely
var storages = getAttachedHandlers()
.filter(handler -> handler.characteristics().canInsert())
.filter(handler -> !handler.characteristics().isVoiding())
.toList();
Negative amounts should throw, but empty resource should no-op in most every case we have. This is what I had on my local version
public final class TransferPreconditions {
private TransferPreconditions() {}
/**
* Ensures the value passed in is non-negative, throws otherwise.
*
* @return The value passed.
* @throws IllegalArgumentException when value is negative.
*/
public static void checkNonNegative(int value) {
if (value < 0) {
throw new IllegalArgumentException("Non-negative check failed: " + value);
}
}
public static void checkNonNegativeAndIndex(int value, int index, int size) {
checkNonNegative(value);
Objects.checkIndex(index, size);
}
/**
* A utility method to validate if the resource and amount are valid values for {@link IResourceHandler} usage.
* <p>
* Typical use-case is in handler insert or extract implementations to determine if the operation should short circuit.
*
* @param resource The resource to check.
* @param amount The amount.
* @return {@code true} if the resource and amount were not empty (resource's method {@link IResource#isEmpty()} and the amount is greater than 0); {@code false} otherwise.
* @throws IllegalArgumentException When the amount is negative
* @see ResourceHandlerUtil#isEmpty(IResource, int)
* @see ResourceContainerContentsHandler#insert(int, IResource, int, TransactionContext)
*/
public static boolean validateHandlerInput(IResource resource, int amount) {
checkNonNegative(amount);
return !ResourceHandlerUtil.isEmpty(resource, amount);
}
/**
* A utility method to validate if the resource and amount are valid values for {@link IResourceHandler} usage.
* <p>
* Typical use-case is in handler insert or extract implementations to determine if the operation should short circuit.
*
* @param resource The resource to check.
* @param amount The amount.
* @param index The index the handler is currently operating on.
* @param size The size of the handler.
* @return {@code true} if the resource and amount were not empty (resource's method {@link IResource#isEmpty()} and the amount is greater than 0); {@code false} otherwise.
* @throws IllegalArgumentException When the amount is negative
* @throws IndexOutOfBoundsException When the index is negative or surpasses the size of the handler.
* @see ResourceHandlerUtil#isEmpty(IResource, int)
* @see ResourceContainerContentsHandler#insert(int, IResource, int, TransactionContext)
*/
public static boolean validateHandlerInput(IResource resource, int amount, int index, int size) {
checkNonNegativeAndIndex(amount, index, size);
return !ResourceHandlerUtil.isEmpty(resource, amount);
}
}
Anywhere there was a ResourceHandlerUtil.isEmpty() call in that version, the empty was handling the negative check. Since it is not anymore, we need to do that separately. I changed mine from
if (ResourceHandlerUtil.isEmpty(resource, amount)) return 0;
to
if (!TransferPreconditions.validateHandlerInput(resource, amount)) return 0;
( or the index one where relevant)
I think that the characteristics are a solution in search of a problem; for now I'm gonna remove the supportsXxx methods, they're easy enough to add back later given that they can be defaulted to return true
I hate the characteristics, half of them are not useful at all and are micro ops, others are incompatible with eachother, the only ones that make sense are insertable && extractable
which are trivial getters
I'm opposed to anything that's a bit mask
and strongly so, they're a pain to debug and opaque when exposed as api
so a number of further questions:
- Do we want to throw or noop when inserting an empty resource? Personally I would go for a throw, as it is usually a logic error to attempt to insert an empty resource. And it will save us countless
if (resource.isEmpty()) return 0;statements. - We agreed to have both int and long getters for the amount and capacity. I would personally make the long one abstract, and the int one default. This simply makes more sense since returning a long is easy even if you work with integers. Happy do discuss this if you disagree.
1, Current transfer api would just nop right? I think that should be preserved, its not really saving anything preventing that and just pushes all that isEmpty checks onto the user, who shouldn't really care.
2, long as default, with int as abstract makes the most sense to me, although, i guess it doesn't really matter, anyone can upcast their int length to long just fine
yes the current api would noop; personally I dislike it, if you try to insert 10x empty you really fucked up your logic and we should catch it
Oh, inserting an empty resource, definately should throw
inserting zero of a non-empty resource should not
I hope to go through the PR later this weekend, bug me if i forgor
- throw on empty resources (ie air) but not on empty stacks
- the highest number type being abstract and downcasting for lesser types would be standard practice
well, not directly downcasting, rather clamping but you get what I mean
I mean wrt I'm fine with either but I think it's easier to implement abstract methods as longs than abstract int methods when you internally use longs
in the latter case you need to override both the int and long methods and manually clamp the int one, whereas up casting from int to long is implicit
i think long being abstract is the least painful for implementors, as the downcast/clamp doesn't have to be reimplemented each time
upcasting/extending from int to long is free, so w/e
thoughts on int maxAmount (currently we have int amount) as the param name for insert etc?
ok I made the changes we discussed
and did a bunch of cleanup/simplification
I removed ScopedResourceHandler because I couldn't find a good name, and it wasn't there in the past. SingleIndexResourceHandler is gone in favor of static methods on RangedResourceHandler
I am tempted to flatten this all into the handlers package
OK the PR is now ready for review
Please dont, in isolation this looks overly separate, but remember there are many more things going in. And having it be a monolithic list helps no one
Let me double check the "resources" package later
Link please
I honestly prefer how it was for ux in regards to getAmount being the implemented method and getAsLong the added defaulted. It did make the implementation of the long slightly more involved, but that was sort of the point.... not to trick a user into thinking somehow this long is transferrable and that we always have the right typing when working with it
We had a discussion on this a WHILE back #1183818213134446742 message
I think we wait till a few more slices before solidifying any flattening, I personally would like to see more of the rework first before agreeing to any particular package structure
First pass didn't get that far will continue later 😅
@flat root your transaction follow up would likely be better without that extends @Nullable Object binding: It just causes IDEA to complain on EVERYTHING otherwise whenever someone has a package info remotely similar to neo's 
I created a draft PR we'll later delete that provides a PR publishing artifact of the current consolidated transfer staging branch: https://github.com/neoforged/NeoForge/pull/2574
(which hopefully builds successfully)
This PR only exists to have a PR published artifact of the current transfer rework staging branch.
Series of PRs:
Transfer: Transactions Review #2414
Transfer: Resources Review #2415
Transfer Rewo...
The two main issues with this are:
- what Soaryn mentioned (though I am on the fence about how much it matters as long as we are clear enough in the docs and have the int variants be the simpler names so that callers by default want to use those)
- The forced up casting just to downcast in most cases feels meh, but that might just be a micro optimization and not really matter
The basic intent should always be an int. The advanced usage, while not necessarily java standard which shouldn't apply to this as java standards aren't really relevant, should be longs. But confusing a user with the very forward facing "here is a long, by the way you can't use this really anywhere" is not a good approach
No, it's the opposite
By making the other method NonExtendable it's less confusing what to implement
Since you always implement the long method
What I'm saying is, that they are presented with a method that is always a long. When EVERYWHERE in the api is ints
so it creates a disconnect
Well that part of the API isn't int
It only benefits a handful of mods
¯_(ツ)_/¯
if the implementation side is identical I really dont think it matters for the implementor
And it is
If you want another example of this, see RandomAccessFile, etc.
If you are talking about "read" that isn't quite the same
as they encode 257 values for it
0-255 for the byte value : 256
-1 for a cancellation
what?
I am talking about long length() and read taking 32-bit lengths
since arrays can only be 32-bit
Since we know there can be more than Integer.MAX_VALUE of a resource, but we only allow transfering up to Integer.MAX_VALUE per operation, it's not that dissimilar
These two paradigms though are built for completely different set of users as well as extension.
Ours should be built with the intent to be used by arbitrary dev of varying skill levels
RandomAccessFile isn't really made to be extended, (it can be) but built for users who are familiar with file IO or similar systems
The audience is vastly different
I don't see anything confusing with the way I implemented it. The amount can be long for arbitrary users, but we provide convenience methods to clamp to an int. Anything else is more confusing
You should probably write @Nullable Void there
Show the full class? I wrote a test and I didn't see a warning. Maybe look at the test
Yeah I see that too
It's the right thing to do. Whether it works in intellij or not is a different question. If you don't show the warning I can't tell you what to do
... I did? #1183818213134446742 message
I'll do a PR-to-the-PR with some doc improvements. Generally I'll try to move anything that is aimed at implementors into impl specs
And adjust the summary to be more from the perspective of a consumer
CombinedResourceHandler is intended to be subclassed, yes? Given the protected methods
It can be yea, but should mostly work out of the box from what I remember
We should prefer ints in the API where possible. This is consistent with most if not all other APIs we expose, and vanilla itself.
long-returning APIs are inherently a more advanced use case and most handlers will not ever exceed the 32-bit limit
(Consider that most inventories are chest-sized or smaller, as they are e.g. machines with a single input/output slot)
I'll admit that it is convenient having the ""default"" be long, but we should be designing for the majority use case, and huge containers like Storage Drawers or AE systems are very often an exception to the norm
AE doesn't have IItemHandlers with amounts > Integer.MAX_VALUE, I don't understand where this myth comes from.
This is purely for having full visibility to the actual amount in external storages, and not designing an API that is gimped from the get-go.
Can we stop re-litigating the points that were already litigated and led to having long getters in the first place?
I wasn't implying that it did; I was just talking about huge containers in general
Since it's just a drawn out re-hashing of things that were already said.
What we agreed on, was there being int getAmount() and long getAmountAsLong()
I agree that there is indeed a need for such an API, but the shape that is being proposed was not what we agreed on
That was under the assumption that an implementor would have to pick which to implement. Which is inferior to the updated version where it's crystal clear what an implementor has to do
without extra effort
But now the caller is going to inevitably wind up with issues
insert(something, getAmount()... oh it complains it is a long? I uh? cast int!
And that will result in an immediate crash if it was just slightly over an int
Not everyone uses IntelliJ too
(I don't know if Eclipse/VSCode/whatever presents in the same order)
The point of asLong was to be crystal clear your intent was to be outside normal scopes
in both implementation and consumption
What does that even mean. "in normal scope"? It's obviously in scope to have amounts larger than Integer.MAX_VALUE, otherwise the method would not exist
Normal scope = normal use
Presenting long getAmount() as the "default" getAmount implies that we're encouraging longs everywhere, which we're not
MOST mods are going to have a capacity, or amount that is 64 or so. Or a several buckets. Not ... 9 quintillion
what does this have to do with encouraging?
There ARE handlers that store more than Integer.MAX_VALUE
there's nothing to encourage, it's just a fact
We made the choice to limit individual operations to int, which is fine
But acting like it's somehow magic that a handler could contain more is mind boggling
ultimately, providing an amount clamped to int is purely a convenience due to the operation limit
Consider that you're a beginner modder, and you look at IResourceHandler because "the guys in NeoForge are smarter than me", but if you see long getAmount() you'll LIKELY assume that is the method you're INTENDED to use
I don’t like this. As I said above I don’t really care which is abstract but getAmount should be the int one regardless of which we have people implement
Swapping the naming is whatever, I think, yeah.
Like I said, I'm not arguing against a long-sized amount/capacity accessor
But just from what the methods do, it is more transparent this way, given the getAmount doesn't convert anything, while getAmountAsInt does.
But ultimately I'd be fine with swapping it around
I'm arguing specifically against the naming seeming to imply that the long-sized accessor is "preferred"
getBackingAmount / getTrueAmount? (Not too happy with those however)
It is if you want to know the amount
It is not if you want to conveniently have a clamped amount for transfer
But whatever, we can suggest swapping the naming back
I already have; I left a review comment on the interface saying to do that
But implementation wise it's definitely better to only have one of the two as preferred-to-implement
No, you asked for reverting it
Which isn't the same
Is it not?
no
I was assuming that "revert" in this case was "revert to the agreed-upon int getAmount() / long getAmountAsLong()"
I'll clarify that
There - I've updated my comments to reflect my intent better
The more important part is default-implementing the int-getter and marking it as NonExtendable so implementors dont get as confused on what to do
Since IJ will not default-select that when you do "implement methods" in your subclass
It probably does it purely because of the default implementation alone, heh. The annotation is just a marker
That annotation really does nothing for autocompletes
Yeah; I'm fine with getAmount being defaulted to getAmountAsLong as a convenience
Since I think MOST people won't be implementing the IResourceHandler themselves
even if they do, they can blindly hack away at it and pay no attention
and will get it right
whether they use int or long internally
Clearly this discussion is an eternal argument, so we should make it be byte[] getAmount() where everyone has to length-check the array and figure out how big the storage is /s /s /s
So BigInteger?
stab
the only mod I've seen use that was Draconic Evolution iirc 
Here are my suggestion for doc improvements in PR-form: https://github.com/Technici4n/NeoForge/pull/2/files
Why is that? The real amount is the long amount. People using the int amount should know that they might lose some information. This is why the current naming and implementation were chosen. If the javadoc is not clear enough regarding why using int is ok, we can ofc adjust the wording
it feels rather meh to have 95%+ of call sites having to have getAmountAsInt (and I don't mean about typing it, I more mean about reading it as part of control flow logic)
Well, if it's for an unknown handler you might be losing some info
The naming is there to remind you to check if that's ok or not
Even a simple "is full" check should really be using the long amounts, for example
what if we just made everything ints
so the JVM stack
It is worth considering that the characteristics might be more useful in the item context than it is in the block one. For example, I use that feature to determine if an item is allowed to be in the drain slot of a battery block, or to see if I can skip it during an inventory wide scan of items for energy distribution
It’s also a fairly trivial thing to implement that makes it useful for this use case ^, a use case I don’t think is uncommon
The issue with the characteristics is that a lot of the ones that were there are just... highly ambiguous. The "voiding" and "infinite" ones in particular, jump to mine. The supportsInsertion/supportsExtraction, sure, that's useful for performance reasons -- but seeing as both should default to true that's also a trivial thing to add later if it turns out it's desired
I'm not sure how "voiding" was ambiguous; it means that any insert will succeed but the handler will never contain anything
Nonetheless, we should prefer the characteristics approach because I guarantee that we will in the future want to disambiguate certain handlers beyond a simple "can i insert? can i extract?"
So... A pipe?
I can very easily picture something like a factory manager showing an option to skip trash cans on a network
"If you pull this item out, it MUST go somewhere and it MUST NOT be deleted"
Wouldn't it be nice to know that info without having to do simulations all the time?
(simulations wouldn't let you know whether it goes poof, so I am not sure of the point of this sentence/question
)
Useful, yes. But the bikeshedding that will happen is best done later once the rest is in
Agreed
You could have every insert succeed, and the handler never contain anything, and have it still not be voiding because it's immediately putting the items somewhere else. Or storing them somewhere not immediately accessible to the thing that put them in but may be accessible from something else. Or processing them slowly and making them visible somewhere else after 20 ticks. IDK. The issue with "voiding" is fundamentally -- what does it mean to "delete" the items? Must they be tossed without side effects? What restrictions are on those side effects?
Transactions allow you to inspect the state of an inv after an insertion
The characteristics are only approximate. You have to "simulate" in any case. Thanks to transactions however you can "simulate" a lot more things
I left you two comments. Regarding the CombinedResourceHandler this might also address Orion's concerns. It will work fine with dynamically sized handlers, just assuming a fixed size.
Still. Pipes that don't allow extraction will not show an observable item after you insert one. So the definition of "voiding" would have to be different than that.
aight will address
Yeah that I agree with. But also from a gameplay perspective a lot of the proposed use cases don't really make sense
Alright another grammar nazi comment: handler's not handlers
Yeah I keep repeating that, since it's the opposite in German 😄
Genitiv hehe
I see that there hasn't been any discussion yet on the methods in ResourceHandlerUtil. Did anyone have a look yet? In particular wrt which methods to keep and which to remove
I just saw a bunch of commented out stuff
Yeah cause idk what I need to fix and what I need to remove
To be fair I also didn't really think about them
And if it sent it elsewhere? It might not be voiding it just because it isn’t there
Yeah that you can't see regardless
But yeah I don't see the use case anyway
Is a matter condenser really voiding if it's gonna produce a matter ball? 😄
Well, can be added later anyway
Now you brought it up, that was the very example I was also thinking about 😄
I'd say technically no, if the definition really is the item disappears without any side-effect
Agreed. My point was more about the fact that one line of nano’s wasn’t relevant
pup do you have further thoughts regarding int/long? I don't want to design the API in a way that tries to "ostracize" long users
IDK about ostracize. I was a bit indifferent, but the point about checking if something is full only being "correct" when using the long version is a good one
My take is that if using the longs doesn't cost you anything (e.g. if you're doing an amount < capacity check) you should use the long helpers to ensure correctness
An example of where that's on display is a method like ResourceHandlerUtil.getRedstoneSignalFromResourceHandler
Yeah, or ResourceHandlerUtil.isFull
Or similarly a BC-like gate that checks for 50% fullness (amount >= capacity / 2)
Okay hm. What is the intention for the extract helper that takes an index?
The javadoc seems incorrect either way (Extracts the first resource from an {@link IResourceHandler} that is not empty.) since it will only extract from that one index.
I can't quite think of a use case for that one
All the ones with a stack factory are methods that were present before in some fashion. This allows us to make one control logic, and have the individual resource types bounce through that with something like ItemResource::toStack or ResourceStack::new. We need the IStackFactory back for this though as the point was to provide a way that you could construct the stack type the consumer needed.
In regards to the extract with an index, I seemingly missed passing the index into the extract method on the handler extract
Hm? I don't follow, the signature of the utility method has the index, it just seems like it wraps IResourceHandler#extract with a filter on the resource, is that the intent?
I meant the handler.extract should be using the index
Let me double check where it is used
ItemUtil.extractItemStackAtIndex
yeah that handler should be passed the index
It however constructs a stack from the given index, resource, & amount
So something like ItemStack extractedItemStack = ItemUtil.extractItemStackAtIndex(handler, resource -> true, i, 1, transaction); for the hopper logic
I just didn't notice the issue in tests, since its logic consumption internally is only doing 1 as the amount
... ?
That is the one
The handler.extract.... it needs the index parameter passed through
We use that in the hopper logic
Mainly that I don’t think having long be getAmount makes sense as I think it will be confusing to users. If you really want to have the int one be getAmountAsInt and not be getAmount I think the long one still should be getAmountAsLong
The reason it doesn't have the index on line 376 is because our sole consumption of it atm is the hopper logic, which only takes 1, so I'd never see it misgrab the rest of the indices
So I didn't notice the bug
But the wrapped aspect is that it constructs a stack of what ever type the caller is wanting
What is confusing about it? That's what I don't understand. Hopefully the javadoc makes the intent clear enough
I think it is a footgun, that there is no real reason having. The main use cases people will have is transferring, and people who just start modding don’t necessarily read javadocs, and more so may not know Java well and will just downcast the long to an int
When do you even use the amount when transferring?
Like I understand it, but I also have seen plenty of people just do the apply suggested fix type stuff before
Most people should hopefully use a move(a, b, r -> true, null) call
We could do getAsLong and getAsInt, that I am not against
I don’t remember offhand, I know I use it a decent bit, but that might be the “amount needed” part, which I do understand your point that it is more accurate to use longs there. But that just furthers my suggestion to Soaryn at one point that having a getNeeded helper that calculates the capacity minus amount is very helpful from my experience on my own handlers
That is what I am proposing if you feel strongly that int shouldn’t be getAmount
So the thing I feel strongly about is that the long one should be the abstract one
That’s fine
I just think regardless of the name of the int one the long one should have AsLong. Though thinking about cases it is probably best for both to just have the AsType so that people are more likely to think/read the docs where we can provide higher level examples of which scenario should use which
For consistency I suppose indeed that having both getAsLong and getAsInt for the names is reasonable
Hah, 
Agreed then
And I will try to take a look at the state of the slice sometime in the next two or three days and leave a review
That is fine by me too
getAmountAsBigDecimal my beloved! 😍
I'm sorry I don't have that UUID amount of fluid
Formatting likely should be done as a utility really outside of the interface itself
i think there was that fluid amount formatting discussion but that IIRC only pertained to fluids
yes, agreed
generally there's a buuuuunch of stuff that can be built on this
Can you do me a favor sometime @loud stirrup, you had a fancy "invalid item" codec that you shared at one point, think you could try to make it generic and take in a resource codec to do a similar thing?
Is using Math.addExact and catching the exception on overflow too slow compared to doing the overflow check myself? 🤔
Ah, I was looking at Longs only, not LongMath
It'd be good to know if it actually overflows since ResourceHandlerUtil.getAmountAsLong can stop iterating further indices if it does indeed overflow already
Although I suppose I can just test if amount >= Long.MAX_VALUE
and break
w.r.t. the codec: I am not sure it can be that generic, I'd have to think about it. The ItemStack one relies on the ability to actually store the offending ItemStack as a component on an "invalid item" ItemStack
Well we have the IData resource one
so in theory we could at least grab those separately
Yes, but you'd need a registered datacomponent to attach it to
The primary issue is that given you want a Codec to return List<T>, you need some representation of an invalid T that still conforms to T
I did previously think about contributing the one for ItemStack upstream, but NF would have to register an item and data components for that and I think we generally only want to do that opt-in
Unlike the other aspects, that one is less player based mechanic changes and more installation management which I would imagine being fine
No it's about Vanilla compat, I believe
Something about the id allocation, I forget the details
COULD possibly have a thing to determine if there was content previously added before initializing that item
But I have no idea
I'd like to see it upstream, but not super necessary
This sounds beyond cursed
It's something to look out for. In the context of resources in general, we can't provide a generalized codec anyway (i mean really generalized) since we have no registry of "resource types" at the moment.
If provided codecs only ever come out of a factory method, I suppose one could add a factory for replacement resources (that get access to the underlying Dynamic that failed to deserialize, for example)
But that's something rather advanced
Working on a PR to the PR to restore the helpers...
getCapacity is a bit of weird one
I'd have assumed that getCapacity(IResourceHandler<?>) returns the "general" capacity, but the current impl will use the resource that is in any given index
Not sure it matters greatly, but is a bit unwieldy to explain in the javadoc, heh
"Taking the currently stored resources into account" might be a way to look into wording it, but yeah it is an odd one to specify 
true
I had initially written something along the lines of {@returns the general overall capacity of the handler} but that wont do
people might mistake that to mean it returns the remaining capacity
Did you implement it that way with a specific use-case in mind?
OH I see
We don't actually have an empty resource 
nope
Yeah ok ok is ee
Hence the second one which is what they can look for
which COULD be empty
Or should be able to at least
yeah true, i should document the caller can pass empty there
to get the general capacity
We'll have to write unit tests for all of these 
We should be able to see the final slice (misc)
And gametests 
as I did a lot of them
(Never got aroudn to flipping some with expect vs actual params)
For the helpers at least there are so many cases you really want JUnit
I can see a lot of use in having gametests for e,g, custom-itemhandler-hopper going into a composter
Otherwise it takes aaaaaaaaaaaages to run
Oh of course
Probably just overall a good mix of both
Yeah
The ones that had a level requirement like actually testing getting the handler I did gametests
yes
A few I made instanced based (though possibly they were unit tests only in my local)
Anything with a level needs a gametest. We have ephemeral minecraft server, but it has no level
I was just thinking of more testing the vanilla mechanics we expand on like hoppers and composters
I'm not sure it's really that useful
I think it's probably fine assuming both have the AsInt/AsLong suffix
You can look at Soaryn's comments on the PR for hints wrt usage in later slices
aight
should contains/indexOf restrict usage of empty resources or at least specify that they're supported (and return the first empty slot / whether there are any empty slots)
Should return first available "open" index
I would disallow empty resources
It is a valid inquiry? Why not?
If it's specified there's a point to it
Maybe it's ok for indexOf to allow empty, but for contains it's really weird
It's okay to generalize over it+
contains an empty resource
I am fine with it if we mention it in the javadoc, IMHO
Prevents us / or the user from accidentally assuming it is not a valid parameter
Adding empty slots would change the result of contains even though the contents of the handler haven't changed. Weird imo
contains(empty) should either always return false or always return true - I don't think throwing is reasonable since it's very easy to get an empty resource "by accident"
I mean if you call it deliberately with an empty resource, it's probably what you want though
we require non-empty resources on almost all other arguments, Monica
and do throw
I may be misunderstanding something then - my assumption was that an "empty resource" was e.g. an ItemStack of air with count 0
Consistency is generally good, so in lieu of me not understanding go ahead with being consistent 😅
Well generally if the meaning is not well defined, we want to catch errors like that early (which is good imho)
but if we can define the semantics, it's fine.
I'll just document the current behavior
Well, you might accidentally call it with an empty resource
Mhmhmhmhmhm
That is the argument for disallowing empty in a bunch of other places, i.e. that yes we could return a noop answer but it is likely that the programmer made a mistake so we might as well report it
So I guess the question is: as a caller, are there situations in which you might have an empty or non-empty resource in a variable and indiscriminately call contains/indexOf
Say you have a filter UI
The semantics of it are quite different if you think about it, OTOH stuff like indexOf(null) works the same way
Calling contains with an empty resource because the filter was not configured, is likely a mistake
I literally use contains(handler,Empty) now
It isn't that the filter is a mistake, it tells me the first index that items COULD be received in
Why is that useful?
It can be useful for UI shit potentially
But, it might be overall a better tradeoff to have explicit methods for it
UI or if there is no filter at all.
There is always a resource present
Even if it is empty, that is a resource
So, my understanding is that soaryn is using contains(empty) to determine whether there is space in the handler
(without necessarily needing a transaction)
Note, a rough estimation of if there is space
If on an external's mod I could use this as a pre-emptive don't open up transactions that would just result in rolling back anyway
uh you'll still have to actually try insertion since contains is still just a heuristic for "has it space"
correct
and it'll be super fucking slow for some mods
This isn't for inserting
you're better of using the index-less insert
Yeah; I was under the impression that you could view a handler as basically a set of resources, e.g. (a, b, c) for a handler with 3 slots, of which the empty set () is indeed a subset
Technically, the creative player context was also using this if (!ResourceHandlerUtil.contains(handler, resource)) { but that one may need to be redone anyway
I can see the utility in being able to ask whether there is space in the container for a visual state like this
(though it looks like that implementation checks for empty before hand)
I would be okay with it as a separate method
The chance to accidentally get a false-positive because a caller made a mistake and checked for EMPTY is not insignificant, IMHO
I'd rather have people discover that problem early
The main thing I'm concerned about is a potential for confusion if they e.g. are thinking of sets
where contains(empty) would technically always be true
Well they'll quickly discover it isn't 😄
yeah 😅
lol
Hm. hasExtractableResource -> handler.extract(resource, 1, temp)
That could cause a false negative for handlers that only allow extracting specific amounts, but there's little one can do. Trying to extract Integer.MAX_VALUE could technically work but might trigger rather expensive checks too
That would always be the case no matter the util or not
If we really wanted it to be "accurate" we'd probably need a hasSpace or similar method on IResourceHandler
Ehhh that wouldn't go well in implementation I feel
Mhm
This is kinda the opposite of hasSpace
We COULD do extract the amount currently present
HMHMHM
So we do a manual iteration
That might actually be more sensible
Likely a decent middleground
Right, I thought you were still talking about the has-space case 
It already iterates all slots anyway
STILL would have some false positives, but that is still fine
since it offers a filter on the resource
But yeah, the "most accurate" solution would be to have additional methods on the interface for querying space/extractability/etc of resources, but as Soaryn said it would be kinda meh in implementation
Ok, my PR-to-the-PR with the reactivated utils: https://github.com/Technici4n/NeoForge/pull/3/files
ok let me have a look
OK I think this is pretty good overall, but I left you a few comments
Yeah idk about the amount/capacity utils either. Certainly can't use those in transfer itself
I would just delete them for now but if you prefer to keep them that's also fine 🤷
Alright, I checked the large rework PR from earlier and couldn't find any real usages for getAmount/getCapacity (just one in tests)
so I'll yeet those
One less thing to test, ig
Those were the ones that even I was unsure about
in concept they made sense, but they can be added later if needed.
We should still have the stack factories so that we can use those methods to create an itemstack or fluidstack (or other) directly rather than assume always resource stack
Otherwise we allocate unnecessarily in the Item and FluidUtils which need the respective stacks
@flat root put the interface name back as what it was named It is supposed to be IResourceHandler. That is really aggravating. -.-
We had this discussion before, and you "taking over the PR" isn't meant to be you apply all naming as you see fit. You are supposed to be making it smoother...
I thought he’d renamed it to something completely different, but IResourceHandler to ResourceHandler is fine
I hate the “I” being everywhere
I am definitely not for changes without discussions, but seeing that now we have to waste effort to remove the I in all locations as well as introduce inconsistencies it is not ideal.. As that was the "main" reason we were requiring it to be sliced which lead us here.
what's the problem here now 
well, not sure how that's a problem
the author of a PR cannot be complelled to add nor remove the prefix
the discussion ends there
Yup
no buts
And no butts
yes thank you silk now back to #squirrels-🦊 
Should make a note tho to fix the earlier slices that are using I rn
We shouldn't as those ACTUALLY are using I to reduce user contention
as Resource is a MC thing already
My point was this was a PR that we had finished and needed more review, we had to go through an absurd amount of hoops and suddenly we are not considered the PR authors now
The stance is that the PR writer can be using or not using I as they see fit
To be clear, the only reason I wrote my PR with Is in the first place was because Neo did. Otherwise it would have been free of Is from the beginning
sure but it doesn't matter? the rules are simple, PR authors can decide whether they add Is or remove them based on the time of the day and maintainers are required to disregard that
And it's worth noting that the vote was for maintainers to take over the PR
Therefore the authors of the PR are now the maintainers
As they are the ones authoring the PR, i.e. writing the code
Hmmmm. I noticed we do not actually specify that getAmount should return 0 if the resource at the same index is empty. I think that should be a requirement.
Made a change request for that: https://github.com/neoforged/NeoForge/pull/2571#pullrequestreview-3126468178
Alright, here's another PR-to-PR to fix some minor docs issues: https://github.com/Technici4n/NeoForge/pull/4/files
your maintainers still brought it up for the non maintainer pr tho
Yeah, I think since this originally was owned by a community member we should use their naming as opposed to suddenly changing it
Merged. CI failed, but that was because of missing tags on your fork
Yeah that makes sense to me. I thought it was in one of the early versions of this rework, but not sure which exactly
yes and the SC intervened, referenced the decision and stated that it doesn't have to be renamed. I didn't make the rules 🤷♂️
and we aren't going to spend the next 10 hours arguing over something that isn't left up to discussion. as I said, the rules are the rules, so please move on
I ran out of patience before I finished reviewing the ResourceHandlerUtil class, though I will try to finish doing so later today or tomorrow. But I went ahead and left my comments on the rest of it and what I have reviewed so far of the util class
Personally not a fan of writing 3+ @throws clauses on every method
If we write too much javadoc people will stop reading (we're dangerously close to the max I would personally read - we might even have exceeded it in some places already)
But without them, people will assume it won't crash when we use it in other places.
While documentation fatigue is something to be wary of, the main point of all of these is to be perfectly clear what you can and cannot do with it, lest we fall into the same trap of the previous handlers where it was the wild west in some cases.
Especially in cases where it involves crashing behaviour and not just quirky results
I don't want the implementations to feel forced to throw, especially given that we explicitly ask dynamically sized handlers to be lenient with their index management
implementations should however always throw for empty resources or < 0 amounts
Whatever cases we have that they should always throw for, we should document so people make sure they do and that they throw the correct exception for it
The implSpec is important, but I am not sure the type matters
callers really shouldn't catch those, but OTOH we can still specify those if we want to
It might be simpler to specifically say "use TransferPreconditions.blahblah to validate the parameters in your implementation" or some-such
Such verbosity 😄
generally we will have the problem of having to specify both what implementors and consumers should do in one method javadoc.
that'll bloat it
I explicitly don't want callers to start catching them tbh
I agree callers probably shouldn’t be catching them, but over time I have moved towards considering listing the known and expected types as better API documentation. Especially as if you aren’t just reading the plain text but using your IDE as the viewer then the extra verbosity in separate sections isn’t hard to ignore for if you don’t care about it.
yeah I am somewhat in the camp of: if you dont adhere to the constraints, it's UB
but we still recommend implementors to do X
But anyhow. We can specify it, but that would also mean we have to actually specify the type of the exception although it doesnt matter
I also am of the view point that when we have these common cases like resource empty or index negative etc, that the docs for the exceptions thrown is trivial to copy to all relevant javadocs so doesn’t really add more maintenance issues if we decide on the wording before applying to all spots
it's a lot of copy-pasting to a lot of places
I mean that's not that much of a problem, i think
I'd not copy it to the utils
As a consumer it's pretty much useless info though. You'll never be able to rely on it.
Yea the utils don’t matter quite as much
But it is helpful to know potential expected cases you can throw for implementers
yeah I also see it as info for implementors, for the most part
And what types of exceptions to throw for certain things.
we'd want implementors to throw these exceptions (well, we'd want them to use the preconditions :D)
that can go in an implNote
with the implSpec saying which ones to throw? 😛
I'm not a fan of these annotations in general
If you are going to add it why do it as an implNote instead of a proper throws thing…
and I meant the preconditions specifically
the reason I don't like these annotations is because they split a potentially readable sequence of 3-4 paragraphs into a mess of remarks
Ah
as a side note the implementation of CombinedResourceStorage is difficult to follow
Yes, I spent a bit working out if there was a bug in it because of the doc for bartended being opposite of what it does
I think using local variables would help improve the readability of the logic though for some parts
the original is not super clear either:
public class CombinedInvWrapper implements IItemHandlerModifiable {
protected final IItemHandlerModifiable[] itemHandler; // the handlers
protected final int[] baseIndex; // index-offsets of the different handlers
protected final int slotCount; // number of total slots
Basically, an array of handlers, then which index is the starting point for that handler in this set if we were to flatten them all to a single "array" of resources
each index is actually the ending point
Exclusive*
(or equivalently the starting point of the following handler)
btw I'm gonna make all 3 fields private since the handlers and offsets are accessible from the protected methods already
That is fine, I think they are only protected as that is what we were moving from
Nope
well if you need it, i guess you could store it yourself in your subclass ctor
Yeah, I also was going to suggest flipping the index -baseIndex[i] < 0 around (in get handler index?) to have it be subtracting the index until I understood that it was end index not start index for the baseIndex
Not sure you'd need it, given the point of this is to treat it all like one giant handler.
Or PR a simple protected getter for it
I mean for subclassing if you need it in the subclass for some reason so that you can avoid storing it yourself. But I don’t think there is a need
Can't think of one off hand
the subclasser might want to iterate the handlers for something 😄
maybe on-commit logic or whatever
Mmmm subclass in constructor save the count
most pointless bound checks ever? 😄
then you can iterate with getHandlerFromIndex
(the handlers array already has its own bound checks lol)
Looking at the history, apparently there was a point we did
return index >= 0 && index < handlers.length ? handlers[index] : EmptyResourceHandler.instance();
@loud stirrup I believe I addressed or at least replied to your comments on github; do you agree? (i.e. please check 😛 )
at least the earlier batch you sent
yes will check
and w.r.t. the combined handler methods
I was pondering to suggest very verbose names there since "handler index" and "external index" (or public index, we have no real name there) overlaps in name
and that confused the hell out of me since some methods take handler indices, other take the external index
so, on the one hand yes, but on the other hand it doesn't really matter... if you look at any of the other methods you'll immediately understand how this is meant to be used
yeah this is a bit long
I'll merge implSpec and implNote, not a fan of the current split display
alright, most comments have been addressed; @hidden geode please recheck yours especially, and I am happy to discuss stuff here
Yeah we can't really specify IndexOutOfBoundsException for index
Since some don't throw 😄
implementations must properly support transactions
no
what do I need to do to keep transactions out of neo, rile up an angry mob?
Commoble, I know you hate transactions but I think you’re in the minority
I feel like there's three devs with mods that can actually use them and they're making everyone else rewrite their itemhandlers to support them
did you look at what you have to do or just kneejerk reactioning to the name?
They have definitely improved some of the logic I have
ok so I will start by saying I have zero experience with implementing item transfer mods
that said, how would I (correctly) do something as simple as have a pipe equally distribute its outputs to targets if I don't have transactions?
There has been countless disucssion on this topic, months of work, and 2 landed PR's (including the start of Transactions) before now. We are not re-discussing this
Fabric has transaction and iirc, they have been fine with it and mods use it
In fact, in Forgecraft, there was a regular modder who said they hated transaction until they used it on fabric and now are pro transaction
For the most part, the implementation changes you'd need:
when you go to set the value, take a snapshot first
On the calling side, open a transaction and pass that through. Commit if you like what you got
so now instead of just setting an itemstack in a list, I have to implement "snapshots" and "commits"
you don't really implement a commit you just call commit
snapshotting is a journal, and is very simple to setup
I was anti transactions before I looked through tech's POC PR, and then after suggesting some tweaks got them to a part where I think they actually simplify things for the general modder
You now can also always assume it is written to rather than am I simulating?
hence the snapshot. But you can provide callback info for when a commit fails or succeeds
I assume we provide some default framework for simple storage blocks to use that doesn't require every modder to implement their own transactional handling?
You mean templates? then yes there were some
idk about the name templates, it sounds more like copy this, than inherit this
Too early. Package naming discussions have already been agreed to happen at the end after the slices are together 
Your implementation would change to just always assuming you’re not simulating, moving the code where you make a final change to a different method (like, moving blockEntity.setChanged) to a different method, and then the snapshots. Snapshots can be literally anything. If you really wanted to, you could just use a compound tag and write to a new tag to make a snapshot and read from one to revert back. You should already have that code made if you’re using a block entity. On items you can just use the data component as a snapshot and do it that way. It’s extremely simple
And more than likely, you can just extend an existing template and just use their snapshots and commits, which makes the changes you’d want to make to it a lot simpler since you don’t have to worry about writing code for simulations of more complicated handlers
yeah "template" and "journal" give me enterprise vibes
they are good names
for me a template is something you look at or copy to make your own not something you inherit from
journal i think is indeed fine, better than the fabric name
Okey will do
ok so @cunning mulch I went through your comments again, but left a few as non-resolved (and non-addressed) as I am not convinced
can you go through them again and tell me if there's any you feel strongly about? I think for most I am happy to implement the change if you have a strong preference
same for your comments @loud stirrup --- unless you want me to mark them as resolved given that you approved the PR already
They weren't?
and so on
Well just resolve the ones you fixed directly anyway
I left the ones where we might want to discuss more open
I responded to some of your comments in the unresolved ones, there is a couple I need to think about a bit more, but I will do that a bit later (in like an hour or two when I have a bit more time)
sounds good, thanks
regarding offering a helper to combine handlers or not, we could potentially have ResourceHandler.of(...) similarly to List.of etc but I am personally not a fan. And putting it in the CombinedRH class seems a bit confusing (should one use the ctor or the helper)
it's also a behavior difference for dynamically sized handlers whether you go through the ctor or the helper
That's why my suggestion also says to make the ctor protected
And I think it makes it way more pleasant to use if you just have a list of handlers that may be anything from 0..N handlers
we could make the helper take a list and the ctor a vararg 
would definitely be good to get more input on this one though, as maybe it should? Personally from my experience it is helpful for pipes with in transit items, to have some sorts of checks that they can guarentee are not as performance intensive to get a "good enough" estimate of if it is even worth trying to insert. Like sure it might accept some even if it is at the capacity, but imo it is an edge case that isn't worth the performance hit. Which is why I don't really like the stating it is an "estimate". Like sure I can understand having it so if it returns MAX_LONG then it is an estimate and might still accept past that point, but for lower values, imo if they don't have that somehow calculated... let it be an estimated upper bound
taking a list instead of a vararg is less helpful tbh, when it comes to one vs multiple
IMO getting the capacity is not really cheaper than trying to insert (especially if the handler will reject the insertion)
meh, potentially
I am thinking from past experience, with things like storage drawers, etc
and if you really care about performance you should use the indexless insert
They will probably not be able to return the capacity in a bullet-proof way either (at least not quickly)
I think approximate is not great wording either, it's not gonna guess at it, I think
I'd call it a hint
yes, the indexless might help some of the issues, I am just thinking about all of this method: https://github.com/mekanism/Mekanism/blob/1.21.x/src/main/java/mekanism/common/content/transporter/TransporterManager.java#L66 where I had to add extra cases to determine whether simulating is necessary, or whether I can "trust" that a previous one is more or less still up to date
yes hint is better
but still having some way of stating that ideally it doesn't return values lower than actually can be inserted would improve it for me
In case of Long.MAX_VALUE it likely would though
I don't care that much if it is exact, but if it can actually fit 100, and just returns 10... that just feels wrong to me
You could special case it, I suppose
yes
(in the docs)
that's sort of what I am proposing
I see the benefit for having it not have to be perfect capacity
In that if the returned value is less than {Long,Integer}.MAX_VALUE
It is a hint for the upper bound that can fit in that index
but having it be less than amount (when not at the edge of the boundary) just feels foolish to me
exactly
I think the current statement of it being a hint is just from experience that it's sometimes not trivial to know if the capacity is 0 or 1
(assuming the slot can accept an item based on some more complex calculation)
But in that case, you would indeed return 1
I know, but that is why I mainly just want it to be more clear that it is a hint of the upper bound
Which fits with the upper bound hint
anyway, be back in a bit
ok how about this?
* This function serves only as a hint on the maximum {@linkplain #getAmountAsLong(int) amount} the resource handler might contain.
* It might overestimate the capacity if computing the exact capacity is expensive or dependent on other state,
* and the capacity might be larger than the currently stored amount, for example if the capacity was reduced below a previously stored amount.
* The only way to know if a handler will accept a resource, is to try to {@link #insert insert} it.
@hidden geode I don't understand your point regarding CombinedResourceHandler. The behavior is well specified as it is, even for dynamically sized handlers.
I'd be more concise:
{@return a hint about the maximum {@linkplain #getAmountAsLong(int) amount} the resource handler allows at the index}
<p>The returned capacity may overestimate the actual allowed amount, and it might be smaller than the current amount.
<p>The only way to know if a handler will accept a resource, is to try to {@link #insert insert} it.
I mean that's just saying the capacity is potentially too large or too small without giving reasons why 😄
as an implementer it matters why though (or maybe not? since these are just examples)
I don't care either way so I am happy to go for yours if pup is ok with it
you should already have a reason to overestimate it though, as an implementor
so they wouldnt need us to give them one
No it is not.
Consider a Combined handler with the following inner handler:
- Variable sized handler containing 10 different stacks, and one empty slot for the next stack (so index 0 to 10)
- One single slot fixed size inventory with say a pickaxe. (so index 11)
If you now extract fully from index 9 the variable size inventory changes (from size 11 to size 10, indexes 0-9, filled on indexes 0-8, empty on 9), any interaction with index 10 is broken and will cause an exception, most likely index out of range, even though the combined handler still reports that 10 is a valid index to use for interactions with it. Which in summary violates the ResourceHandler contract that states that size() indicates the amount of slots that the handler has and that any interaction withit with an index within that range should not throw.
Or am I missing something here
Realistically you don't even need the second inventory in the combined handler, but it is there in this example to illustrate that it stays put in the original index list, and that the offsets array is not updated, and the size of the combined handler stays the same.
we ask dynamically sized handlers to be lenient with their bounds check; a properly implemented dynamic handler will just make slot 10 a no-op even if its currently reported size is <= 10
How would it know though to be lenient?
Should it also be lenient for slot index 20?
Or 50?
it should be lenient for previously reported sizes as the very least
That seems a bit vague and really not a great idea
if it never reported a size > 20 it doesn't need to allow slots >= 20
Except that there is no way to know this.
I am not aware of any IItemHander implementation currently which has this requirement and I don't think asking for somebody that implements this to track all of their previous sizes
Is a good idea
this is not a new pattern, we always do
int size = handler.size();
for (int index = 0; index < size; index++) {
so the handler cannot just start throwing exceptions for indices smaller than the size
Additionally it is already broken if you do an insert, except even more.
of course it can know this, it overrides the size() method
because now you can not access the new slots of the combined handler
that's fine, it's the behavior that was specified
with a ranged wrapper you also cannot access all slots of a handler
That is no where specified at all.....
we specify that the size is constant in the wrapper class
Except that combined is not a ranged inventory and does not pretend to be one
no, but it specifies that it treats all internal handlers with a fixed size
And making it behave like one, defeats its entire purpose in my opinion
Then provide a way to detect that properly
no, we already decided it was out of scope for this PR
Else I don't think that adding this class is not a good idea
what? orion, you're exaggerating wildly
this is often used for combining machine inventories or whatever
saying that not supporting shifting indices for dynamically sized wrapped handlers "defeats the entire purpose"
is kinda missing the purpose 😄
Okey, yeah I know that. But as soon as you have any dynamically sized inventory you can't use it
And if you, you will get subtle undefined and in my opinion undesired behaviours
the default convenience handlers should handle the 80% case
and in this case, it's probably the 95% case
(at least)
Sure that is fine by me, but for the remaining 20% they should not be a food gun, and properly throw an exception if tried to be used in an undesired case
probably 99% even
depending on what you do it's perfectly fine to use this class for a dynamically sized handler
Especially not a foodgun where it is not obvious that the behaviour all of a sudden changes.
I have come up with more cases in my head where it breaks with a dynamically sized handler, then ones where it does not. Yes there are for sure ones where it works perfectly fine. But there are many where it does not. And that seems to me personally risky. Especially if it can be solved by adding a simple bool to ResourceHandler: #isDynamicallySized And checking for it in the constructor
Orion, combining dynamically sized handlers that can change size at any time is already a footgun.
Conceptually!
Yeah, 100%
This actually makes it not throw up in your face
OK, I am tired of discussing this. We are not gonna remove the class, and we are not gonna add characteristics at this stage of the rework. What do you suggest we do?
So I am not sure why you'd favor runtime crashes
But there is no way to know, that you are holding a footgun at the time you are given an ResourceHandler
Hence the safe way out
Just use the size at time of construction
Which will not runtime crash
With undefined behaviour?
It's not undefined
What are you talking about 🤔
It's the opposite. It's well defined 😄
I find this behavbiour completely undefined.....
I mean, you can have an opinion on it