#Transfer rework (IItemHandler, IFluidHandler, IEnergyStorage)
1 messages · Page 2 of 1
yes and if that's the case I'm good
Plenty of inventories cannot sensibly implement this
As in, if I had to implement that it'd lead to wacky undefined behavior
setting anything is not great api design. does not give the author enough control over how the interaction occurs. which sucks because this is how all the existing heat apis work ;-;
I already have to do enough weirdness just to pretend to have normal "slots"
if the default is "only one stack, any transaction that tries to insert more than one thing at a time receives a 'not valid' result" that is fine
that's what I mean with not giving modders additional work
The idea would be that you could have more complicated transactions, you'd just wrap the simple operations like you decribed in a simple API
transactions could be simplified on the dev side using someone elses storage, but they are more complicated to implement on a storage than traditional handlers
because it forces you to defer all unrollbackable actions to some final commit method
no you can't ensure that. you can't ensure that a complex operation will be able to be rolled back. the only way to implement a fallback for transactions is to disallow complex operations
Wut? No, implementors of the API would have to handle transactions still for that reason. But people querying a container -- or using a pre-made implementation -- wouldn't have to
Also, transactions aren't "rolling back" an inventories state...
The whole point is that you don't have to do that
let me say it this way: if I have to implement transactions into Ender-Rift I'm not porting the mod
I have no energy for it
nor the use case
well no, itd be easy to have like, TransferUtils.simulateInsert() for those who need it. But there is no real way to simplify how storage is implemented with transactions
thats the big problem
and im not sure it can be avoided
but ender-rift is a powered inventory -- insert/extract operations cost power
Well, you give people building blocks that they can make their implementation out of that hide the transaction stuff away
so you can run into the situation where there's enough power to simulate one insertion positively
but once you commit to the first insertion, there's no power for the second
Right, and handling that situation sensibly is exactly what transactions are good for...
yes but I don't want to be the person that has to implement them
that's precisely the kind of situation that I am against: the "fuck you implement transactions if you have your own inventory implementation"
no thanks.
yeaah i get that sentiment. i sorta feel the same way. transactions definitely complicate how one has to handle their storage
I mean the alternative is to disallow complex operations entirely -- which now forces the issue onto the people querying your thing instead. They have to figure out how to handle otherwise unrecoverable states, when they need to implement A only if they can also implement B, etc.
yeah thats just going to have to be what forge deals with im afraid
the alternative is to make it opt-in. if someone's requirement is "I must be able to insert these 2 stacks, or nothing at all"
in my pipe mod I don't immediately insert items into chests after I simulate them
I simulate insertions into connected chests, then if I find sufficient room for the inserted items, I queue up the items and let them run through the tubes along the path to that chest
then however many ticks later, when the items actually reach the chests, I physically insert them
if there's no room for items at that point then I either try to reinsert items back into the tubes or eject them into the level (so if you get extra backwash from the chest filling up then they can get rerouted to other inventories in the network)
the answer for Ender-Rift will be "nothing at all" every time.
there is no such thing as opt in transactions. if transactions are part of the system, everything has to use it or your storage wont interact with other things properly
not true, the default transaction provider can be a fallback that disallows complex operations
and what does 'disallowing complex operations' look like
if transactions are part of the system then you WILL end up with an entirely separate ecosystem of mods who don't want to support transactions and use a different itemhandler system
a new standard popping up is not going to happen, thats stupid
well, we already have at least two mods that would use the transactionless standard
I mean that's also a fair case, honestly -- have transactions, but make a special implementation that only allows single exchange transactions and disallows any further operations after the first
That would actually be pretty easy to make
I picture a transactional API looking somewhat like this: ```java
transaction = inventory.beginTransaction();
transaction.insert(....);
transaction.insert(...);
if (transaction.isValid()) // This will be false if the request cannot be satisfied, which will be the case always for the fallback
transaction.commit();
else {
transaction.cancel();
}
this is just the builder system i described, and has the same issues tech noted
I don't care what the API looks like, just the concept
a transaction is a stateful object
that lets you declare your intentions
and then optionally choose to execute all of the actions at once
at some point in the API, there's a way to query if the transaction can be performed in whole or not
Giga: you could make a wrapper to fabric's transaction stuff that does what you want here
if transactions are going to be an issue causing people like commoble to just ignore them, then itd be better not to have them. i dont want to personally have to deal with mods being incompatible with mine because they were too stubborn to implement the standard api
All you'd need is an implementation that allows only one operation and then just returns a full remainder after that. It would be completely valid normal transaction stuff too
this reminds me of AE2 refusing to invalidate caps
we should avoid creating the same problem a second time
why do you think I'm saying that the default should be a fallback that works just disallows complex operations (anything that does more than one operation, be it multiple insertions, multiple extractions, or a combination of insertions and extractions)
wouldn't that just result in any mods that do complex operations simply not functioning with the fallback?
The issue is that if that's the default -- then very few people will implement one that allows complex operations, and so nobody will actually be able to do complex operations
there's no way in which you can expect the average modder to implement complex transactions correctly for their custom chest variant
And we're right back where we started
Luckily they don't have to...
They just use the premade class for a fixed size inventory or whatever
because the system you're describing still requires devs to rewrite how their mods work to defer un-rollback-able changes to the commit method. at that point you might as well just implement transactions
but that's the only way to do transactions safely?
yes, the only way to do transactions safely is to make sure nothing physically changes in the world until commit is called
yes, so the only way to have a fallback, is one that disallows complex operations, because that's the only thing you can do safely via simulateInsertion/simulateExtraction
otherwise, every single use case that cannot use the stock ItemHandler implementation is fucked
Right but having the fallback disallow complex operations means that nobody can actually use complex operations...
no?
So you're right back in the current nightmare
If most modders don't implement them... then you can't rely on being able to do them
If it's the default, most modders won't implement them
I would much prefer to have complex operations normally, just provide default implementations that handle them for you
the default ItemHandler can support complex operations
So, for your example, the folks just adding a custom chest would never have to touch it, it would be implemented for them
yes
I used a bad example
a simple chest can use the default ItemHandler, so it can receive the stock "complex operations supported" handler
But if you have a fully custom wacky inventory (like mine... acts as a FIFO buffer), you'd have to implement it yourself
Or you'd have to specifically not support it -- which, I point out, you can still do pretty easily with a transaction system...
Neo just wouldn't provide that as a default/fallback
let's rephrase
I'm okay with having no default in IItemHandler (or whatever replaces it)
nothing's replacing iitemhandler
I'm okay with ItemStackHandler having a complex-operations-allowed default transaction handler
although I suspect it will cause a large number of mods to be buggy
because they implement processing logic on insert/extract by overriding the methods from the reference implementation
The methods they override would do stuff on commit instead
And sure, you could wrap it in a nice API so it looks similar to the current stuff
IMO, the average modder shouldn't have to be aware that transactions exist
they should be something you choose to use if you are ok with raising the usage complexity
The average modder isn't doing that sort of custom logic...
*or if they are necessary to safely and sensibly express the logic you want
there's plenty of mods that have ```
new ItemStackHandler(9) {
@Override insert
}
iitemhandler is deprecated for removal in 1.22
ew, why
because you shouldnt use it
why not
What are they actually doing in that override logic though?
the vast majority, are implementing insert/extract conditions
because its worse, devs should use the immutable resource storage system. idc what you say about transactions cuz i understand the drawbacks there, but itemstacks and fluidstacks are both very flawed as units for transfer. you just dont care about why that is
okay iitemhandler isn't deprecated
Sure. Those would be implemented in a different way in a transaction world. People probably still wouldn't have to touch transaction
it's deprecated in your PR
yes
your PR is non-canon unless and until it's merged
any storage rework pr will deprecate item handler as part of it
why are you assuming any storage rework PR will ever be merged
maintaining a deprecated iitemhandler sounds like more work than just deleting it outright in some future version
well that's the point of this thread
the discussion here is about redesigning the APIs
the APIs don't need to be redesigned
yes. everyone but you is okay with this change
so we are talking as if the design would be happening
I just happen to oppose making the API transaction-first, and forcing people to implement transactions when all they needed was insert+extract of 1 stack at a time
The replacement of iitemhandler with the newer resource API is, imo, separate from the discussion of transactions
^
itemhandlers are simple, why do we need to make them convoluted?
itemstacks go in, itemstacks come out
because there's people with use cases that are convoluted :p
you havent even used it and you're deeming it more convoluted
I remember a PR some years ago
And people mutate those itemstacks when they're not supposed to and then you get wierd bugs -- it's not at all obvious who owns which itemstack
may be one of you
The new API looks a lot like IItemHandler
someone had a minecart / train thing
and they wanted the train to stop ONLY if the train would be able to insert every single stack at once
Transactions or similar stuff is an entirely separate issue
yeah the storage class looks very similar to iitemhandler now
just using immutable resources. thats really all ive changed about it so far
the proposed API is still simple -- in fact simpler in some areas
solves many annoyances I have had in the past
anyone has a link to the PR(s)?
ive tried convincing commoble before, the idea of using anything other than itemstacks in and of itself is convoluted to him :P
nothing is pinned
here
i have no power to pin things
nice
like my only complaint at a glance with the whole thing, is using "stack" for fluids, which has always annoyed me because fluids aren't individual objects you can "stack", and it's an extremely pedantic complaint with very little value :P
transactions though, that I have opinions on :P
i really understand the concern about it being convoluted. i thought the exact same thing when i wrote botarium, which is a library trying to abstract storage from forge and fabric into a lib. But after implementing some default containers in botarium and really understanding why the change was made in fabric, i have come to believe that immutable resources are a much better alternative. i implore you to try it before you complain about its usability
I can't wait for immutable resources
one of the most annoying things right now with Ender-Rift
is that I have to trust that people won't mess with the preview stack returned from getStackInSlot
Yeah that mutation confusion always caused me issues
I used to have a .copy() in the method
but it was horrible in combination with AE2 storage bus
Luckily itemstack copying is now a lot cheaper so now I just defensively copy everything... but, well, not everyone is going to think to do that
cheaper only in the case of a complex stack
And at that point, might as well use immutable resources and have it all be a lot simpler
when the stack has no components, it's equal or slower
(the tag would have been null, but I think the component map isn't)
well i dont think the component map will ever be empty again lol
lots of stuff is part of components now
in a stack of like, dirt?
with no custom name or anything else?
isn't the count outside components still?
isnt the stack size part of that now
yeah
like, stack limit i think is a component ? tho idk if thats set, it might just be defaulted
yes but it's not omnipresent in every stack
the default comes from the Item
a basic item/block stack should, in principle, contain zero components
it's an Integer component
setting a different max stack size on one stack, just .put()s a value into the component map
let me confirm
hmm okay so
it really wouldnt be difficult to provide a default implementation of storage snapshots. its actually alot easier with the immutable resources cuz you dont have to copy anything
could do like, SimpleItemTransactionParticipant whose snapshot is just the exact same class that forge provides for default item storage anyways
idk, im going to continue the PR without transactions for now, but id like to revisit this at some point
Considering transaction seems like a separate concern. I'd like to too, but that can be another PR
yeah itd be impossible to have that, but it would be possible to just say 'if your storage is simple, just use our provided storage class that handles snapshots for you'
how do you abstract
try (var tx = Transaction.openOuter()) {
var extracted = storage.extract(water, 1000, tx); // does a simulation under the hood
// by now you are guaranteed that extracted is usable
tx.commit(); // oops, non-simulated extraction actually returned less; resource duplication??
}
this is just one simple example in case the simulation is not actually trustworthy (unfortunately this is a real issue) 😛
im not sure what you mean here
IItemHandler { beginTransaction() } // no default, you have to choose
abstract ItemHandlerBase implements IItemHandler {} // no default, you have to choose
final SimpleItemHandler extends ItemHandlerBase {} // defaults to a default transaction processor
yes, that is pretty much providing a template implementation similar to ItemStackHandler that handles all the snapshotting
giga I'm afraid that the transactions you are thinking of are extremely limited and of little practical use
what hes thinking about might be enough for just itemcontext? not sure
a transaction is an operation that spans the entire set of storages; it's not just a fancy builder for a series of individual operations
I literally have no use cases for transactions, Tech
so I don't know what people's use cases are
so my default thinking, is how I have seen database transactions
any deviation from that is an implementation detail
giga can you link your handler class?
you still have to implement some "transaction provider" for your inventory
somehow
I am realistic in that I don't believe the ecosystem is going to accept the need to recode every inventory under the sun
so pretend that my pseudocode works in whatever way is necessary
and that directly excludes transactions
IMO, it is possible to have standard item handling and transactions coexist
and IMO, we need to keep it like that
the "implementation details" are where all the problems with transactions come from 😛
yes its possible to have someone use storage in the same way they were using it before, but not without changing how the storage class is written. its impossible
every implementor of a transactional transfer interface has to deal with snapshotting their world-state
and in many cases, they need to transitively snapshot stuff
oh and
the syntax in this example implies that it is possible to interact with multiple inventories within a transaction. IMO that's a huge ball of mess that will never work correctly
its exactly how fabric works and works fine
see this message
I have seen this message. I don't think the transactions you think of are viable without me wanting to quit modding.
that's why I really want a fallback that tells transaction users "sorry no can do."
but then devs like commoble will get an infinite amount of reports of 'your storage doesnt work properly with x pipe mod, and they say its on you'
it will work correctly
so long as the pipe is ok with only inserting one stack at a time
the pipes literally only insert one stack at a time
thats not really the problem
this type of "lower common denominator" approach sucks
I'm modding for fun, if transactions are mandatory, it would suck out all the fun of working with inventories.
because every inventory that wants to use proper transactions will also need to support partial transactions
so nobody can actually rely on transactions
what do you mean with "partial transactions"?
here's my situation: ```
begin transaction
inventory1.insert
inventory1.insert // can't satisfy this. the insert is rejected (inserts zero items)
(I mean I could, I jsut don't want to implement the logic that would be required for me to do that)
i think theres a world where i could make a builder like system work in a transaction environment
whats ur issue then
cuz implementing this would also require you to defer unrollbackable changes to a final commit method. it just wouldnt need you to handle your own rollbacks
the fact that implementing transactions requires one of:
- the ability to track system state in parallel to the real system state
- the ability to roll back changes that have already happened in-world
well what if the changes havent happened in world yet
then you have option 1
option 1 seems unavoidable since it's the definition of a transaction
yes, and I'm asking for a "simple single-operation transaction provider" that automatically rejects any insert/extract after the first one
the problem is that any mod that uses that will simply not function with advanced transaction consumers
or an API that makes implementing this take minimal brainpower and time
I'm okay with that
which i think could happen, but yeah would inherently limit what one could do with your container
if someone MUST do multiple operations on my inventory at once, sorry it wont' be compatible
their mod won't work with mine and that's fine with me
they are free to blame me
I will simply tell people "no time or energy to implement this, sorry"
because the alternative is that I still have no time or energy
is this what you have in mind
so I just stop porting my mod
I am not really suring what you are arguing for or against ghz
I think techs and my point is: supporting transaction is a whole-world affair that you can't get around. so let's not do that in NeoForge
Agreed
I will repeat my original message
if transactions become a thing, please have a fallback that means modders don't have to implement transaction handling in every custom inventory
what you're asking for isnt possible. if transactions exist, something must change in the storage
I want to implement insert/extract, and have everything else done for me
which, given my inventory makes in-world changes on insert/extract
means I won't support multiple operations on my inventory during one transaction
and I won't support multiple transactions happening at the same time
What you're talking about is more or less batch operations against a single invenotry as an optional operation
the second your storage makes an in world change it cannot be rolled back
so you have to change how you implement insert/extract
which then leads us back to the issue at hand
well yes, but you can offer an optional batch operation method adjacent to it if you can satisfy it fully internally
but it has quite the narrow use case
is there any advantage to optional batch operations besides performance?
Simulating against multi-slot inventories
that have internal logic related to mutually exclusive content
for reference, I'm not htinking of batch operations as the use case for transactions
the only use case I can imagine for them, is a "crafter" type machine (processor or whatever)
where you insert 1 of A,B,C, and then get D in return, so you check if you can insert A,B,C all at once, or cancel if not
compressing drawers
simulate extraction of (10 iron ingots, 90 iron nuggets) in face of a compression drawer containing 10 ingots
but the use case is VERY limited
since you cannot propagate this upwards
other cases are machines with multiple input slots (imagine slot 1, 2, 3) where slots 2 and 3 accept ingredients based on the content of slot 1
batch operations work in most cases of itemcontext :P. which is the main thing im having issues with rn
is there some documentation of the transaction system in fabric that I can take a look at, at some point?
and if so, does it have a "minimal viable product" implementation to show how much boilerplate is required?
Generally if you implement storage local to your BE you just use the pre-made classes
the core concept of fabric transactions is that your impl needs to support taking and restoring snapshots IIRC
All transactions require you to do is the following:
you tell the system when a change is about to occur to the container and give it a snapshot of its current state
you tell the system how to restore its state from a snapshot
you tell the system what to do when the changes should be finalized
and then you also have to make sure you do not prematurely cause in world changes
yeah I think it would still require 60%+ of the core feature of my mod to be rewritten
which I'd have a hard time setting aside time for
understandable
itd be a big change for most people, and transactions arent the most straightforward thing to understand
yeah precisely
oh god you linked my flowchart 😄
I feel like I did a decent job at explaining but it's probably still too technical
yeah its very overwhelming understanding it
i dont understand something tho, i feel like a builder system would be alot easier to understand for devs, and you could probably fit a transaction system on top of a builder thing
you would just have to have something in the builder that allows you to make another builder from the builder's current state
challenge for you: implement CombinedStorage using a builder system
(I don't think it can be done)
i would overlay the transaction system over it and simply use that 😄
the builder system wouldnt be required, itd just be an optional way to make it easier for you to make a transactional storage system
so, the builder would only be used by implementors of storage?
pmuch
I doubt that it would be shorter than using SnapshotParticipant at least
didn't fabric have a transfer-debating channel or whatever.. at this rate we need one too
this is the channel, maty
itd eliminate the first and second step of this. because builder would be made right before the change is made, and would just be discarded if you wanted to roll it back
no need 🤷
how would you port this function to a builder system?
@Override
public long insert(T insertedVariant, long maxAmount, TransactionContext transaction) {
StoragePreconditions.notBlankNotNegative(insertedVariant, maxAmount);
if ((insertedVariant.equals(variant) || variant.isBlank()) && canInsert(insertedVariant)) {
long insertedAmount = Math.min(maxAmount, getCapacity(insertedVariant) - amount);
if (insertedAmount > 0) {
updateSnapshots(transaction);
if (variant.isBlank()) {
variant = insertedVariant;
amount = insertedAmount;
} else {
amount += insertedAmount;
}
return insertedAmount;
}
}
return 0;
}
(this is just the standard SingleVariantStorage#insert implementation)
the storage class that gets passed to the capability would be a wrapper around a builder system. The builder itself would have the modify methods but also methods to read its current state (getResource and getAmount). The wrapper would store a builder by created from the builder storage. when updateSnapshot() is called, it stores a new builder built from the stored builder as the snapshot, and when rollback happens it it just overrides the original builder with the snapshot one. basically this method body would just be replaced with updateSnapshot() and return builder.insert()
technically speaking this would make the snapshots mutable but uh, making it not mutable would further complicate it and i think the goal is to try and make it easier for devs trying to implement storage
not sure I understand
@Override
public long insert(T insertedVariant, long maxAmount, TransactionContext transaction) {
updateSnapshots(transaction);
return this.builder.insert(insertedVariant, amount, transaction);
}
public BuilderImpl createSnapshot() {
return this.builder.builder();
}
public void readSnapshot(BuilderImpl snapshot) {
this.builder = snapshot;
}
public void onFinalCommit(TransactionContext transaction) {
this.builder.commit(transaction);
}
Then the builder would have the insert/extract methods, methods to see whats currently in it, and a method to create a new builder from its current state. this should be easy for most devs, they just have to pass around a copy of the list they are likely using for storage. But this basically takes the worrying about what the current state of the storage out of the hands of the dev, and makes it so devs dont have to worry about the current state
hmmm
idk, anti transactioners will probably still not like this solution, but i think its easier to manage :P
and if you wanna do what giga said about failing after the first insert to avoid having to implement a method to copy the builder, you just make the builder copy method return a no ops builder :P
idk if it's easier to manage
in any case we're almost surely not doing transactions for 1.21
they can come later if we want to
if they come later i dont think they are happening at all
thatd be a second breaking change to storage
Yup and it went on and on and on and on and on 😄
that's half of my point
hey all things considered, this wasnt that bad
no idea what went down in fabricord tho
Well, bikeshedding 😄
I don't like the term bikeshedding as it exists now
because whiel it does mean something deep down
people tend to throw it around to refer to stuff they don't care about
oh you have no idea how bad it was
(note: I'm not saying it wasn't bikeshedding there -- it probably was. I just don't like the term XD)
well this isn't really a breaking change, in the sense that you get time to adjust
yes
@flat root with this, is there not a risk that the fluidstack that the resource was created from could be modified and therefore modify the resource itself? shouldnt it be copied?
ah it is
mb
Hi o/. Me again. So there has been some debate about how Energy might be modified a bit to adapt to the new handler change. Currently its its own class. This is great for simplicity as energy is a pretty simple concept. But some feel that energy is already treated pretty similarly to fluids and items and that it would be incredibly useful to have a single storage class be universally used for all transfer. So EnergyStorage would be converted to IResourceHandler<UntypedResource> or (more likely) an interface that extends it, ie IUntypedResourceHandler with the resource removed from the extract methods. Some default interfaces can be provided so there is little to no burden on migrating, the methods could all be made to be relatively the same (ie, there would be an ISingleUntypedResourceHandler that basically looks exactly like the existing energy storage).
The major concern though is that while it would be relatively trivial for a dev for a dev to implement this, it does add a bunch of junk that 99% of energy handler implementations wouldnt use.
While technically this does overcomplicate the base class, it does technically simplify transfer over all, by making it so developers can completely handle all transfer of all storage with the same methods (with some even provided in a todo TransferUtil class).
Feedback would be really helpful here. I know that @vernal dust felt strongly about this, itd be super helpful if you could touch upon why you would find this change useful
while the suggestion here does suggest using UntypedResource or something like that, using the generic handler class could open up something else entirely even, like perhaps making an EnergyResource class that, by default, just uses the Forge provided one in the default interface but could allow others to implement their own energy storage in a way that is compatible with only their energy while still allowing others to make energy pipes that
IResourceHandler<IEnergyResource>, by default everyone uses IForgeEnergyHandler which is just the handler but its isResourceAllowed method only accepts ForgeEnergyResource, but a mod like FTB Contraptions could make it so they only accept ZapsEnergyResource or something. If a mod wants to make it so their handler accepts all energy but stores it as something else, they can convert the energy in the insert method and store it as something else.
okay scratch the whole untyped resource thing, i really like this . <- This was pup's idea btw, not tryna take credit for something i didnt come up withIEnergyResource idea
what would be the contents of the interface
which interface
if you mean the energy one, not sure tbh. What was said so far was a localized name, a component and maybe a color
Oh, and a conversion rate from whatever the resource is to FE. That way all mods could universally be converted to FE if there is a mod that creates a way to convert it
(Optional way to convert, should definitely be able to explicitly mark as not supporting conversion)
yeah, consider that in principle the energy interface could be used for, like, ember or botania mana [no idea whether those mods would actually do so]
hmmmmm
the talk of a color makes me think
what about a color and a texture - and a way for a resource pack to define the color and texture for FE for mods that don't otherwise have a preference
though
hmm
there's a tension there between mods that want to have their own name and appearance for things but want to still be compatible with everything, mods that want to keep their stuff in their own ecosystem and at most convert to FE so other mods they don't care about can be powered by it, mods that just want to have basic energy using stuff
a resource type does open the door for stuff like pipe networks to carry multiple resource types without converting them
like, maybe what the interface needs is, like, an "advisory FE ratio" that's mandatory just so that containers know how to size themselves [i.e. if it's 100, a container that holds 1M FE should only hold 10k of the other unit], and then a set of flags to define whether conversion in either or both direction should be allowed at all
oh i've invented fluid units per bucket haven't i
i do wonder if there's any scope for an energy system to define stuff like electricity or heat or air pressure that have their own characteristics like voltage, amperage, thermal conductivity, volume, passive transfer to equilibrium with transfer rate depending on the difference between each side's pressure/temperature
probably too many variables to get right in a fully parametric way
Such a system would require treating Energy as a concept (i.e. like an Item or Fluid) instead of a primitive as it is now
Conceptually that isn't a horrible design, but it gives the false impression that multiple Energy systems are compatible
Rather than a "conversion rate", I'd suggest a numerator/denominator to encode a rational number, which represents how much one joule is in the system. As the reference point, we could assume that 1000 joules are needed to smelt a single item
Not all things that call themselves energy are compatible, which is fine. But it does make it easy for mods that want to use energy but dont necessarily want to use energy produced by other mods to stick to their own flavor of energy while still being able to use the networks
So, in this case, a piece of coal has 8000 joules stored in it
And a lava bucket, which smelts 100 items, would have 100000 joules stored in it
(this is, of course, assuming standard fuel times)
Alternatively, we can go based on the hunger system, but I can't think of a more natural way to equate that to something the player would be familiar with
I am against changing energy because it overcomplicates it for 99% of its users
cringe
Scope creep is cringe 😛
but energy resources arent cringe. cmon, itd be cool. I dont think energy is so conceptually different from the other storages that it couldnt be the same as the others. Most tanks devs implement are single slot despite the api expecting a multi slot container, we should just do the same here. and then it allows for the cool pipe thingy i talked about!
Idk who it helps
I sure know who it annoys (= everyone) 😛
What is a simple getAmount() turning into?
it helps devs who want to have their own confined energy generation but not be punished severely for not being compatible with others.
Energy is fundamentally not a resource
then itd help you
it doesn't have slots, it doesn't have components, it doesn't share any of the semantics of items/fluids
it doesn't even "exist"
its just a typeless count
energy isnt a resource because we all accept energy as a monolithic system and say everything that is energy-like or a different kind of energy isnt energy. well thats dumb
How would it help me? I can already use everything that works with energy
because your energy has to exclusively be transmitted through your mod
a different mod could have methods of transferring and storing generic energy. that energy could still be used exclusively with your mod, but itd still work with other things that are designed to store energy
If I wanted that I would have used the IEnergyStorage cap
well you dont want other mods who generate energy making energy that your machines can use
that would still be the case
MI would only accept MI branded energy
Although... If mods actually supported multiple caps they could support multiple energy storage caps
but pipes can transfer all brands of energy
Oh god please don't tell me you want multiple energy resources supported by the same storage
nah, itd have to be converted first
So that's literally no change compared to the current system?
well i guess technically you could make it accept all types, but you wouldnt be able to extract all types. though i guess you could but thatd be confusing
Every change is actively trying to make the design worse it's painful to see lol
shush i like this change
These discussions are exhausting
this is the one change that didnt give me a headache
That doesn't mean it's a good one
but its cool!
Yes, cool and bad in practice are not mutually exclusive
this guys so boring smh
In fact, APIs that get built just because they are "cool" are likely to suck
energy is not a viable surface for this resource system
<rant>
Yes I'm boring because I already designed a full transfer API and every thing you do differently compared to that is an option that I considered and then rejected already
It supports non-primitive counted objects with different subtypes
Energy is... none of those
And at some point it gets tiring to argue every single aspect of the design again every week
Every time I read this thread or FC after a long discussion I have to witness and try to address the damage done to the API
It's generally been fine but that's the mindset I have every time...
"what was discussed this time and how did it affect the API"
</rant>
(syntax error, no matching <rant> tag found in content body)
Fixed 😛
i was mostly poking fun at you, but i get what you're saying. it would have been cool to do this, if onlu just to make energy unified with everything else. the conversation in general started when i brought up the idea of a generic integer storage class. a typeless handler was proposed instead, and then how fluxnetworks stores its stuff with types for energy was something pup brought up that i thought was a cool idea. but yeah maybe wouldnt work here.
I know you were joking. Nonetheless, these discussions do get tiring 😛
I also said I don’t think it is actually something we should do! Just because I mentioned it in passing doesn’t mean I think we should do it
no excuses
ah pup; why did you suggest ITransferHandler 😦
IStorage is clearly the superior option!
Because storage makes no sense for a handler as it might not actually have storage and just be say throughput or voiding etc
that's a special case which is still fine to represent as an IStorage
it is just an empty storage
Doesn’t make as much sense compared to something that handles a given type. Which is much more clear and fits better
Handler is a really vague name. It only makes sense because we are used to IFluidHandler and IItemHandler
IResourceHandler<ItemResource> leads to a duplication of the word "resource" it's just annoying
I have only suggested renames to make things clearer and make more sense! I haven’t actually gotten to api scope yet in a review
and IHandler is just bad
it's fine I just wish it would have been discussed more before being changed at once
IHandle<Resource> is also reasonable. But either way in your impl you then can name it whatever you want to avoid the duplicate names for when you are referencing things
like... I don't get any update or anything and suddenly the core class of the PR was renamed
IHandler and IHandle are horrible names in isolation
Hence the resource part
yes but that is a bad name when compared to a resource 😛
IResourceHandler<?> is fine
from the POV of the caller it doesn't matter that some IStorage may or may not actually have some storage
From an implementer though it makes no sense
even a trash can would typically have at least 1 slot, and many pipes also have a concept of slots
what's the problem with making a storage that doesn't actually store things but delegates storage to something else?
it is still an object that can store resources
whether it stores them internally, or delegates storage to something else, or never stores anything... it is still something that stasfies the "IStorage" concept from the outside
Because that isn’t as clear? Also think day item caps. Those are pure handlers the storage is a data component not part of the actual handler
well you get an IStorage and you can get its contents and change them
it doesn't matter what the backing implementation is
the API gives you access to the storage capability regardless of how the resource actually gets serialized under the hood
The api gives you access to a capability to handle the resource type
handle is just vague
it literally means "we couldn't find a better name so we chose this"
even IEnergyStorage is not called IEnergyHandler
I don't think that anyone ever complained about IEnergyStorage possibly not "storing" the contents?
It should be! I use handler for all my energy stuff and didn’t even remember the base one was called storage till someone brought it up as I only interact with it in two classes
yes because you are used to IFluidHandler and IItemHandler
if you were used to IFluidStorage and IItemStorage it would only seem natural 😛
I have also been confused by IEnergyStorage after dealing with fluid and item handlers
a bad argument would be that we don't want to make too many breaking changes and therefore should keep IEnergyStorage
i changed energy storage to energy handler already bro
pfff you really want to make me ragequit this

we already broke all the methods in the energy storage class, breaking the name is not a big deal lol
Every change is actively trying to make the design worse it's painful to see lol
as usual
well true
getting rid of receive is a large improvement
i think sara has a point on storage not necessarily being the best term for everything this api encompasses
there is no h in my name
names with multiple spellings 
i coulda sworn there was an h 
I'm not happy about IHandler and I'm not happy about IResourceHandler
can we find something better?
Im still a fan of ITransfer :P
that is really bad because it doesn't follow naming conventions 😛
yeah speaking of, no one answered my question when i asked
what is proper naming convention for I interfaces
cuz the I makes it sound like it should be a sentence. otherwise why include the I at all
the I stands for interface
We can try but IResourceHandler is definitely a better fit than IStorage
noooo
but trying to get something better yet could work if we can think of something
Certainly! Here are some additional possible names for a Java interface that can store and transfer resources:
ResourceManager
ResourceStorage
DataStore
ResourceTransporter
StorageHandler
ResourceDepot
DataHandler
ResourceRepository
StorageManager
ResourceCarrier
DataTransfer
ResourceService
StorageInterface
ResourceBroker
ResourceCache
StorageUnit
ResourceHolder
TransferService
ResourceExchange
StorageFacility
so bad 😄
i did the same thing

I think that "handler" should be banned 😛
the question is: what are the resources being extracted from? what are they being inserted into?
and there you see how I came up with Storage
it also makes more sense in high-level operations such as "move resources from one storage to another"
Well, what things arent storage that shouldnt be? like, a void or something? Basically anything that doesnt physically have a number of slots to put things in. But those containers are still being made to return content reading methods. So while they arent storage, they effectively present themselves as one?
capabilities define how not what
you should know this. You are the one who split caps and attachments
the fact that it's a capability isn't too important
this is just the more fundamental concept of an interface / object
you are too worried that people will confuse this with data storage
we can call it a store maybe (the original fluidity name) but that is obscure english
i mean, sticking with the vanilla IContainer or IInventory would fix this lol
you cant confuse vanilla behavior
what is the difference between a container and a storage? 😛
I think that Container vs IContainer would be quite confusing
would it? they are both effectively the same thing so itd be fine imo
hmmm yea
IResourceContainer then?
logically IContainer is a generalization of Container
no resource! only container! 😛

imagine IHolder
IBox is perfect! lmfao
they are all quite funny
fundamentally this is what it comes down to
I would actually say that capabilities define what an object can do rather than how it does it
this is just about API (what) vs implementation (how) separation
obviously the fact that we only have 3 capabilities and are reworking them doesn't really help us with a precedent
so let's see what AE2 does... MEStorage, ICraftingMachine, GenericInternalInventory, IInWorldGridNodeHost, ICrankable
it does seem odd that storage was the only usecase anyone ever introduced for caps
seems so versatile
it's too versatile for most use cases 😛
mekanism has a bunch of handlers, IConfigurable, IAlloyInteraction, IConfigCardAccess, IEvaporationSolar, ILaserReceptor, ILaserDissipation, IRadiationShielding, IRadiationEntity
I don't like the naming convention that is used here 😛
it seems weird that a block entity would be an ILaserDissipation
armor is, blocks are laser receptors
what else would it be? ILaserDissipatable or something? lol
classic aidan
speaking of mek, lets add a unified heat api to neo >:)
Oh god no
I'm just saying that these names are not what I would have chosen, based on how I usually name interfaces
so @cunning mulch , does IContainer fix the implication of data serialization by making it more in line with how vanilla communicates storage or do you think we should keep looking for a clearer name
Isn’t a vanilla container the thing for menus or was that the mcp name and I am getting confused
jeeez how many options
I mean, I chose two of them
yes
oh god transfer handler has 4 votes
Personally, I don't want the vanilla names, and item handler is a storage and transfer component in one
well the good thing is that we will easily see which options people do not want
Fair
Poor IStore<Resource>
Just put it in the acknowledgements
i love that pun...
I included every option I could think of 😄
just to be clear, when we say "the same role as IItemHandler", that refers to its roles as allowing observation into its contents and maximum capacity (getSlots, getStackInSlot, getSlotLimit, isItemValid), and transferring contents in and out of it (insertItem and extractItem)?
Not to add a second unrelated fire, but I did want to discuss IColorApplier/Handler/Provider at some point
Which gives you a non-transfer/storage name to argue about
IColor and UGetColored obviously
UGetStuff<ItemTank>
IResource.Handler
Hmmm
heavens no please
IResource.Handler/Provider/Reciever
For SFM (SuperFactoryManager) I've abstracted item/fluid/forge energy/mek gas/mek infuse/mek etc
ResourceType.java
SFM is a mod that adds a domain-specific language for moving stuff around
EVERY 20 TICKS DO
INPUT minecraft:stick FROM chest
OUTPUT TO furnace BOTTOM SIDE
END
is internally
EVERY 20 TICKS DO
INPUT sfm:item:minecraft:stick FROM chest
OUTPUT sfm:item:*:* TO furnace BOTTOM SIDE
END
public abstract class ResourceType<STACK, ITEM, CAP>
and
public class ItemResourceType extends ResourceType<ItemStack, Item, IItemHandler>
public class FluidResourceType extends ResourceType<FluidStack, Fluid, IFluidHandler>
public class ForgeEnergyResourceType extends ResourceType<Integer, Class<Integer>, IEnergyStorage>
public class GasResourceType extends ResourceType<GasStack, Gas, IGasHandler>
...
(this is indeed a little awkward with a single-key resource type of sfm:forge_energy:forge:energy)
transaction/type stuff with the simulate gets a little weird because poeple want me to add a feature where they only perform an action if the entire action will succeed.
Conside a gear press
INPUT FROM chest
OUTPUT EXACTLY 4 iron_ingot -- "exactly" is a not a real SFM feature yet
currently, this could be done by simulating an insertion of 4 iron_ingot into every slot (simulate) until all ingots have been accounted for, not following through with a non-simulate if any remain
however, if someone wants to do
OUTPUT EXACTLY 16000 cobblestone, 4 iron_ingot, 16 diamond, 256 gold_ingot TO super_duper_crafter
then the transaction stuff gets a lot more complicated
if there's one free slot, a simulate insert for two different stacks will pass for both
barrels are also weird, varying by implementation the stacksize limit adds a lot of complexity
a barrel may be implemented by showing a stack of 10,000 when getStackInSlot, only returning 64 when extract-nosimulate
if someone wants to pull 100,000 cobblestone out of a barrel but the barrel is throttling by 64 per extract that's not a great feel
IResourceHolder<ItemStack> would probably be what would fit with my naming for a generic IItemHandler
So your needing help with an API that isn't forge? I'm confused if the snippets are for a script or ?
hmm yeah that is not reliable 😅
interesting structed transfer language
Would be cool if Lua was used, as that I believe would allow for more flexibility
And it's easily integrable with Java
not that I need help, more just insight into my mod which has some more nuanced interactions with stuff due to being an item movement mod
Ah
You can do this in 1 extract and 1 insert in the new transfer system because resource and value are separate, so you can do storage.insert(stick, 16000, execute) in one go
slot is usually still important, what about a furnace where there's a fuel,input,output slot and a minecraft:oak_log is a valid input and fuel
is there a way to differentiate still?
I guess it just depends on what direction you used to get the capability?
Storage will have a slot dependent and slot agnostic insert/extract method
So you can do both
neat
incidentally i wish there was a way to specify preference per item there for shift clicking. i almost always want to make charcoal, i almost never want to make graphite
[big reactors has graphite by smelting charcoal]
another thing that comes up is trying to enumerate the slots provided by a mod, there's no mapping from insertIntoSlot vs menu slot indices
all the slots in the mekanism machines are created such that the slot index is zero
// draw index on each slot
for (var slot : menu.slots) {
int colour;
if (slot.container instanceof Inventory) {
//noinspection DataFlowIssue
colour = ChatFormatting.YELLOW.getColor();
inventorySlotCount++;
} else {
colour = 0xFFF;
containerSlotCount++;
}
Minecraft.getInstance().font.draw(
poseStack,
Component.literal(Integer.toString(slot.getSlotIndex())),
acs.getGuiLeft() + slot.x,
acs.getGuiTop() + slot.y,
colour
);
}
SlotItemHandler has a separate index field which has no getter, you'll get that effect on almost any modded GUI
might be worth looking at, but not really fundamentally transfer api related
for your thing for now i'd just check instanceof SlotItemHandler and access the index field by reflection
[there's no guarantee that for any arbitrary gui that'll work at all though]
The slot number itself does have a getter tho, getContainerSlot
....why does it have two getters?
regardless, this doesn't change what i said, it's not useful on SlotItemHandler
also it's super confusing that the Slot.index field [index within the Menu] and the SlotItemHandler.index [index within the IItemHandler] have the same name
Legacy :D
Slot item handler is a mess lol. I got rid of the duplicate index thing on the new version of SlotItemHandler and got rid of the very cursed handling for getMaxStackSize
Doing this PR there is so much legacy garbage that has been fun to sort through
Can you make a Slot backed by immutable stacks? 🙂
You can never assume that
Especially for custom slots
I think so? Isn’t the getStackInSlot method in the item handler supposed to be read only? Are there instances of slot directly mutating the stack? How do fabric mods usually handle this?
I didnt notice until now, most of the storage things are still written to use INBTSerializable. Is this how the new templates should be written? So far ive just been using data attachments and data components
INBTSerializable is kinda legacy
Just gonna point to 1035 >.>
I was recently thinking of refactoring it to match stuff like loadAdditional and saveAdditional. is that smth the team is interesting in rn ?
Huh?
nvm 😅
maybe an issue should be opened separately about removing it?
@flat root So Shadows' PR introduced new component based storage stuff, but it uses MutableDataComponentHolder to mutate the stack directly, which is bad with IItemContext. I had written a template using IItemContext already, but im curious as to what should be done with this new thing. Cuz people will get confused seeing 2 different containers that both apply to items but only 1 should actually be used with it
lol 💀
I dont get it, why is this done this way? the if checks are kinda useless. fluidType.getBucket() works on water too
i think the code is just old
"call getBucket" almost certainly used to be "construct universal bucket"
which hasn't been a thing since almost certainly 1.12
Whats the standard we wanna go in for registries in constructors. Like, Shadows' stuff uses the bare DataComponentType, other stuff uses a supplier of DataComponentType, whats the standard
If it needs the ID, take a Holder; otherwise take a Supplier
DeferredHolder can satisfy both interfaces so there's no problem with it; the worst case is that people just have to add an additional () -> value
I wonder if we should start slowly moving things towards Holders
frankly, it depends on whether it needs to be lazy
Meh, new stuff consider it, but rewriting existing unless the system it interacts with changes, I am not sure there is much point
I would generally lean towards "no" unless it's likely that the thing will be interfacing with a RegistryAccess/HolderLookup/similar at some point, where the ID can be useful
blegh, shift key mispress 
I used to be "use holders wherever possible" but I've softened a little bit after the registry rework because DeferredHolder is way more useful now
Yeah hence my comment of evaluate if it is better for new APIs and for existing ones only do so if mojang changes stuff we are interfacing with in a way holders make more sense
Use the concrete type. The supplier makes no sense because it must be immediately resolved for sanity
Why does forge have this arbitrary slot limit for armor? as far as i can tell vanilla does not implement this kind of restriction
well the stack limit for chestplates is 1, so that wouldnt be a problem
but it kinda sets an arbitrary limit that anything in the armor slot has to be not stackable
Beacons on the head is possible
oh good idea, let me test with something stackable in game for a sec
it might be a restriction they set somewhere else, like at the gui level or something
ah yeah, i stand corrected, only 1 allowed
I have a mod idea, Activated Beacon on the head, have a beam shoot out of the players head

Neat
i feel like thatd just give you radiation sickness or something lmao
Lmao
maybe for pumpkins on helmet to behave properly?
nvm just scrolled

yeah iirc vanilla doesn't set the item type limits on the armor slots in the container either, just in the gui
interesting that neo accounts for the placement limitation but NOT the removal limitation
seems like no one bothered to update the handler when curse of binding was introduced
For the slotless extract/insert methods, what do you guys think about just having a default impl for it? like something like this:
default int insert(T resource, int amount, TransferAction action) {
int inserted = 0;
for (int index = 0; index < size(); index++) {
if (TransferUtils.isEmpty(this, index)) continue;
inserted += insert(index, resource, amount - inserted, action);
if (inserted >= amount) {
return inserted;
}
}
for (int index = 0; index < size(); index++) {
if (!TransferUtils.isEmpty(this, index)) continue;
inserted += insert(index, resource, amount - inserted, action);
if (inserted >= amount) {
return inserted;
}
}
return inserted;
}
default int extract(T resource, int amount, TransferAction action) {
int extracted = 0;
for (int index = 0; index < size(); index++) {
if (!resource.equals(getResource(index))) continue;
extracted += extract(index, resource, amount - extracted, action);
if (extracted >= amount) {
return extracted;
}
}
return extracted;
}
Just so devs dont have to have the burden of implementing 4 transfer methods when most use cases just need this
could also just move this to a util method that has this logic
once to only insert into already filled slots, then to go through empty slots
ah thats true, thats redundant
I don't think the base implementation should be doing a stacked insert
that is for modders to override if they want to
we should have a stacked insert helper anyway
this is important to not break vanilla compat
wdym vanilla compat?
hopper into vanilla chest
im not sure how this is relevant tho. the handler wrapper around the chest can just override the method with different logic
idk, I think the core API should be as unopinionated as possible, and stacking into existing slots is already too much for me
yes that would be very useful
another thing: cache the size(), don't query it for every loop iteration
ah true
If the method is slotless and the container has room for all the items, I would want it to deposit all the items.
Having it return some remainder and having to retry inserting until there isn't any is just tedium
while remainder != old_remainder do
old_remainder = remainder
remainder = insert remainder
If I care about avoiding default logic for fitting all the items in, then I'd use the slotted method and do it myself
Yeah it should try to deposit everything
It will do that with a stacked insert too, that's not really relevant
Or rather: it shouldn't be if implemented correctly 😛
yeah, he was just saying that stacked insert is an opinionated way of doing it
the slotless insert method is supposed to be an insert method with distribution left to the mod author. it should try to insert all of the items if it can (which is what the javadocs will reflect when i write them)
stacked insert is one way, a mod like refined storage may choose to see if a slot for the item exists for it already, and if it doesnt make a new slot and insert everything into that one (which doesnt work exactly with stacked insert)
You know, vanilla for its sided containers has methods for telling whether or not an item can be inserted and extracted from a container. It might be nice to turn isValid(slot, resource) into isInsertable(slot, resource) and isExtractable(slot, resource) and do something similar.
I think i finished most of the wrappers for items and fluids, i was wondering, do we want to have the same wrappers for energy available as well?
I don't think that it's valuable to know if something can be inserted or extracted, since you can just try it
Yeah that's the whole point of simulate
isInsertable/isExtractable will just end up running a simulate in the general case, I think
isValid is about "could this item ever go into this slot", not about the current state of the slot
well yeah, the 2 methods would also be a stateless check, and isExtractable for the most part would just return !resource.isBlank(). But there could be a few cases where itd be nice, like in armor where you would want to make sure the resource doesnt have the binding enchantment
i dont think it would hurt to add. i think most implementations of storage just return true for isValid, and most would return !resource.isBlank() for isExtractable. Just more info. If its too much of an implementation burden we could default it?
I think that anything that doesn't have a clear use case should not be added to the API
Im currently stuck ;-;. I cant run patch gen cuz the current version of neo gradle doesnt work on mac, and the latest version of neo gradle is broken with patch gen. anyone got any ideas?
yell at someone to stop releasing broken NG versions
who do i yell at
ah well it seems like orion will get it sorted soon, ill just work around it best i can till then
i have so many javadocs to write 😓
nothing in vanilla does this, default implementation should act like a hopper [always insert into the first slot that can accept the item no matter what]
that way our hopper implementation can call the method
its a completely generic implementation. This is how id expect fluids to be inserted. I can add some non stacking utility inserts as well
Why is FluidUtil so... massive
dealing with stuff like stacked empty buckets is hard? idk
getFilledBucket can probably be yeeted if it hasn't already, it clearly dates back to the old universal bucket API whereas now fluid types are responsible for providing buckets anyway, most of the rest i think has some usefulness though
alot of it is useful, but alot of it is just odd?
i'm a bit skeptical of IFluidBlock and associated stuff [in particular, LiquidBlock doesn't implement it, nor do waterloggable blocks], and i've long said IFluidTank should be removed
i killed fluid tank, and IFluidBlock has been removed in a separate PR already
there's probably some scope for cleanup in FluidUtil itself but a lot of it is pretty important
ah i'm still on 1.20.6
actually i think people were using ifluidtank, i might have to bring that back for the transition period ;-;
the documentation seems to imply devs should be using it as like... a representation of a slot? but only as a suggestion? its so weird
FluidTank is very useful, IFluidTank is not so much
i have an example impl for a single slot fluid tank, so we're good on that front
odd that forge never provided an example for multi slot fluid storage?
the big problem is it's not really reasonable for most purposes to require that the implementation you're interacting with implements IFluidTank, since IFluidHandler already has slotless insert and extract methods
and IFluidTank doesn't even subclass IFluidHandler
iirc the problem is that multi slot fluid storage isn't that well defined, you've basically got three different use cases:
- different slots that are for different fluids, and each one has its own validation function
- second slot is used if the first slot is already full of a different fluid etc
- slots are created dynamically i.e. tinkers construct smeltery
the fact that there's no slot targeting insert/extract functions means the utility is limited, if you want, say, your gui, to let you target a specific slot you have to implement your own handler on top of some other structure that lets you do that
maybe we should have an "aggregating fluid handler wrapper" that just takes a list of fluid handlers and concatenates their slots together, and you can provide that a list of single tank fluid handlers if you need them
(or maybe we should provide slot functions, idk - we've been fine this long without them though and it really does make things worse for tinkers construct and similar cases [another example is the create basin])
well storage is completely generic, there is a generic "aggregating resource handler" that would work with fluids. Since fluid uses the same handler as items, they also have both slotless and slot aware insert/extract functions. there are also slot aware validation functions on the base class, so these things shouldnt limit fluids from being used properly as slotted storage
i'm not convinced that int getTanks and FluidStack getFluidInTank - shouldn't be replaced with a single List<FluidStack> getAllContents. I'm sure Technician will tell me why i'm wrong on this one.
ah, well, i meant the current fluid api is mostly slotless
it does have a slotted extract iirc
unless that's a very recent addition i am certain it does not
oh im wrong
it has a specific fluid extract
what?? has this always been this way
damn, ive been doing multiplatform for too long
anyways, yes the new handler has slot aware and slotless methods, so no more of this weird crap
does it still have the specific fluid case? i remember we went back and forth on whether that was a reasonable thing to want for items
like, an extract method that says what fluid the caller wants instead of what slot
hmm i wonder if it'd be better as a defaulted method on the interface
[like, sure, iterate the slots and find a matching slot by default]
it might help with allowing devs to create more efficient methods for their storage, but im not sure about it tbh
i think we're getting into the same "filter as opaque predicate" vs "filter as something that can be used to structure large containers as to not require a linear search" issue, which is honestly less important for fluids than for items
linear search isnt really that bad at the scales most devs implement storage. it just gets bad when buuz makes a mod 
most fluid containers are only going to have one slot, or a single digit amount at most - things like tinkers construct are extreme outliers
whereas item containers with a hundred slots or more are not uncommon
well the way i currently implemented it is that itll go through the slots till it finds the first item that matches the filter and then itll call the slotless extract method with that resource
buuz? i don't know what buuz has done, the real insane one here is kroeser, did you know every slot of every attached container is exposed in the item handler of ID item interfaces?
presumably also fluid interfaces, but you can easily get thousands or tens of thousands of item slots
i think buuz has a container in functional storage that has an infinite amount of slots
an infinite amount of single stack items
ah, yeah, the armory cabinet
iirc it has weird limitations too though, something about not being able to insert multiple items in a single tick
interesting, it has a cooldown?
i have no firsthand information, this is just something i heard from the chat of a twitch streamer who was trying to use it
gotcha
anyways, its an interesting idea. dont think itd necessarily be a bad thing to allow devs to implement the search method in their own way, but maybe thats not the best approach if we're adding it on the interface directly
interact is pretty important, it's the most reliable way to get "click on your tank with a bucket" working.
tryfill and tryempty are if nothing else called by interact
maybe itd be better to just have devs have a findResource(predicate) method instead that gets defaulted to a linear search
i don't know if we need the one-liner methods that just wrap getCapability
thatd be more useful overall imo
its nice tho D:
pickup and place is... well, what i'm seeing in 1.20.6 provides IFluidBlock support, it might be less necessary when that's gone?
getting an optional fluid handler seems so dumb lmao. why not just have a method in capability itself for that?
I mean
ultimately all it's doing is Optional.ofNullable
if we want to use Optionals we should use them in the capability API itself
right now it uses nullable
i don't think we should have two methods in the API that use nullable vs optional with no other distinction, we should pick one - and evidently we picked nullable
the insane part is that its used inside forge code AND THEY JUST DO .orElse(null)
i think alot of this class will just end up being deleted. ill keep and cleanup the interaction stuff
anyway, most of the code in FluidUtil is in support of either 'transfer fluid between containers' [including the interaction stuff but a lot of it is generic enough to not require one of them to be an item] or 'pick up and place a block in the world' - the latter i think was to reduce the patch surface on buckets, but it's probably also used at this point by modded machines that directly place or pick up fluid blocks
whats wrong with mB ?
if it were to change, I would like to be a highly composite number (12 or 60 looks nice)
Millibuckets are annoying for mods like Tinkers' that want a nugget worth of fluid (when a block is a bucket). It's one of the reasons fabric went with 81000 for its fluid parts.
what about 'each fluid defines its own ratio, with a guideline to pick a number reasonably close to 1000' so people don't have to rewrite tanks
being divisible by 64 would be nice.
it doesn't work for cauldrons (and by extension water bottles)
since water cauldron levels are 1/3 of a bucket
(and water bottles are equivalent to cauldron levels)
Millibuckets are annoying for mods like Tinkers' that want a nugget worth of fluid (when a block is a bucket)
imo this assumption should be changed then. 1 bucket = 1 block
though I am not sure what it should be changed to 😅
That's not even true though: cauldrons are definitely not full blocks (but store full buckets) and even fluids in world don't take up a full block space
I think the real solution is to just accept that fluids are lossy :p
Prime number fluid units when
and yet, fabric "tinkers" does not make use of divisons of 81000
I think 1bucket = 324mb would be good, can be divided by 3, 4, 9, 81 which afaik are the most common dividers in minecraft
The problem with this discussion most times we have it is we never get people with the mods actually uusing fluids discussing it
its always a bunch of third parties who have an opinion but no mod that cares about the result
the right way would be imo to find the commonly used dividers and then find the lowest common product
Which is what that poll is about
Hence why speciifc suggestions are not valuable
Though to be honest, that poll still suffers from that problem where I dont think most of the votes come from people using fluids, I feel a lot of them are just people who have an opinion on the topic without stakes
I think tying the unit to a name is probably more valuable. Instead of saying "9", say "9, gems like diamond" or "81, ingots and nuggets"
or some people with "9, ingots" who don't care about nuggets
Knowing why people wnat the number I think is more useful than just knowing the number they want
For isntance, I think a lot of people wanting 10 just want to stick with what they have already, I nearly answered 5 before looking back and seeing nothing I had needed 5, I just used 5 as 1000 divides by 5
that is a very odd number but I guess that is how rest of the minecraft is. all numbers are made up like what ratio ore processing should use
for me, I am just looking at easily divisible numbers so that we can clean divide them and not worry about fractions when do transfers
36 could be such a number, divisible by 2, 3,4, 6, 9, 12, 18
But knightMiner is right, having multiple units could be better, because my number as oddly specific as it is, forgets division by 5 which is most likely used somewhere
What are all the numbers you want a bucket divisible by
to be fully honest, 3 is my absolute minimal
if we are changing it, might as well do 9 and 4. And 81 would be great but not essential
do you want to keep 1 bucket as 1 block ?
I couldn't care less about 1 bucket 1 block
324 for 3, 4, 9, 81
1620 for 3, 4, 5, 9, 81
I also don't know what you mean by "keep"
no one is doing 1 bucket 1 block right now (except maybe fabric mods)
at least not as a guarantee, just specific fluids do that
I see. A message above mentioned it. I could be wrong
It be nice to have, but its not an essential
I'd rather a unit that is easy to work with
I know mekanism uses 200mb in the ore processing pipeline, so it should be divisible by at least 5 too
e.g. someone earlier proposed 900 per bucket, which I could make work with 90 per ingot as before, 10 ingots to a bucket
I do think the most important thing though is an API for fluid unit display
Before we go about changing bucket divisions, we need a standard for how you show values in tooltips
that would work pretty well. its not very far from current 1000 parts
That way, no one is stuck displaying 1/81000 or other such
I do this in Mantle/Tinkers via JSON, though the JSON is mostly to support other mods
if Neo did it, they could easily register it as part of fluid types
My logic is basically a list of numeric value/translation key pairs in descending order, and holding shift displays in the smallest increment (right now mb) in case people want to compare capacities
Is anyone seriously suggesting to change millibuckets in NF?
That would give us bugs until the end of time.
The bucket amount started with this poll it seems
I believe someone is
The logic is "while transfer is breaking might as well consider it"
experience fluid has a somewhat longstanding "50 xp per bucket" convention
and i believe the last time this came up the terrafirmacraft guy said they use percentages for alloys, so that's a legitimate-ish case for keeping it divisible at least by 100
I agree its worth considering again, though I doubt we will reach a number that satisfies the community enough to finally rid ourselves of millibuckets
I mean, in part this stalled last time because people were throwing around composite numbers large enough that reasonable size tanks would need the quantity amount to be a long and Lex said no to long
I also think trying to force 81 into this will just lead to awkward numbers
I can make my stuff work with just 9 if need be, as long as no one is forcing 1 block = 1 bucket. Though I have heard a few people express desire for that to be a thing
i also don't really understand the objection to making it per-fluid
I think making everyone happy will be hard
you mean each fluid having its own max value?
yeah
i couldn't find any good prefix for 1/900 parts. I am just gonna call the unit "neoBucket" or "nB".
no one wants to transfer at bucket rates as a standard
that sounds too much like a full bucket, given neo just means "new"
I would not worry about naming the minimal unit
I mean, my idea is more "try to keep it close-ish to 1000 so transfer rates and tank sizes don't have to care but it can be 810 or 1276 or whatever and stuff that actually puts fluids in the world or puts it in actual buckets has to do a lookup"
the names can come later
For this to work well you really need to be able to properly format it for display to users
Fraction of bucket display lol
That is exactly how that works, yes 😄
jade or something on fabric figures out the most reduced fraction of a bucket iirc
Value to component list essentially
but
I talked about it here
having fluids be able to define their own units would open up the possibility of e.g. tinkers fluids saying how many ingots and nuggets you have
potions how many bottles, etc
how do you suppose recipes using fluid tags would work?
leaving it up to JSON lets modpack makers standarize them
talking about consuming fluid amounts
the same way they do now
you only tag things with the same fluid units
[by 'their own units' i mean 'their own set of radices for display', independent of whether that means bucket sizes are per fluid or not]
we could leave the units on the serverside API, I could see some benefit
maybe you could manage to convert between two fluids with different units
if you need different quantities of different fluids you need two different recipes [or a composite ingredient type that can associate different quantities with different holdersets anyway]
we'd need something akin to tool actions for that to happen though
some way to say "these are both ingots so they should be the same"
and if that means you can't make use of a tag so be it
yeah, which is part of the reason my fluid ingredient does htat
I have not looked recently to see if Neo's does, or if that even got merged yet
Looks like they went with single size
Though the root ingredient is sizeless
anyway, other than the possibility of going into long quantities after a few thousand buckets, is there any reason not to just multiply together every number anyone could ever want it to be divisible by? i ended up with something like 648000 the last time i tried this
so you can always make your own wrapper that compounds multiple sizes
the larger you get, the harder it is to parse
and no matter how nicely we display, eventually you will have to parse
some usages of fluids will use a partial unit and just scale up the behavior
so you may end up with the smallest increment left in a tank eventually
not to mention, tank capacities
no one wants their tank to list "stores 51 ingots, 45 cheese cubes, or 89 snail heads"
the smallest unit is how you convert from one to another for capacities
honestly for most purposes i'm not sure we can't just display the number of buckets [or of the smallest unit of a fluid that defines itself in terms of nuggets or whatever] as a float
0.190519 buckets isn't great but it's more legible than "123456 droplets"
8100 would be annoying, but it would not kill me if that was agreed upon
my concern is partly someone is going to have 2 droplets sitting in a tank, and wonder what we can do with it. Someone else is going to make a pipe that transfers 1 ingot, and wonder how they transfer 1 brick
an empty tank can display its capacity in buckets, a partially full tank can display its capacity in terms of whatever fluid it's actually holding... which just leaves, uh, smelteries, as the odd one out which has an odd amount of headspace you can put any fluid you want in
at some point in time, any nice unit we make will be split because someone's pipe uses a different unit
smelteries are hardly the only multitank
but fluid pipes are the real problem here
the only other one i'm aware of is the create basin, which iirc holds "up to 1 bucket of every fluid you put in it"
I like to transfer 1 ingot at a time, works nicely with how my mod works. Others like to transfer in percentages of a bucket
If we let people set arbitrary units, someone's pipe will eventually break that unit, and we are back down to the smallest unit in display
This is where the "every fluid has its own smallest unit" idea gains some merit tbh, but that really makes piping awkward
We'd end up with every tank listing its capacity in buckets, and transfer would have to decide whether to go by buckets or by the smallest unit available to the passed fluid
tbh "end users break a pipe that hasn't finished transferring and void an odd quantity of fluid, and it's a valuable enough fluid that they care enough about the odd leftover quantity to want to do something with it instead of voiding it too" is to some extent a self-created problem by those users.
I'm not even talking about finishing transfer or not
lets say I go from a tank that holds 81 ingots though a pipe transferring bricks into a tank holding 72 cheese cubes
there is potential for the destination to become full with a partial unit left untransferred
I've dealt with this before with just bucket tanks and ingot tanks
okay. might be a crazy idea but what would happen if there were 2 units. one bigger and one smaller like how feet-inch works
tbh the solution is arguably "recombine all your odd quantities in a tank large enough to hold them"
you fill an ingot tank with lava, and either have leftover spae or partial buckets
you just described multiplying the standard
[or, if the fluid isn't valuable enough to care, just void it]
and displaying the partial multiple
ah yes, the API is so good that voiding resources is a part of the API
there is no one size fit all solution
like essentially this is the same as the fun little tinkers minigame in 1.12 where i have to figure out what alloying recipes i have to do to use up the odd quarter nugget quantity of something that i got as a result of some sequence of accidental alloying operations
Either we make it flexible and hard for transfer, or we make it easy for transfer but limited
i can either figure it out, or i can void the odd quantities
you had to do that for tinkers because tinkers uses a lot of different fluid values
imagine having to do it for every combination of mods that use a fluid
right now most usages of fluids can stick to divisions of 4 or 10 if they wish and the math is easy
well, it's mainly because 1.12 did a "3/4 nugget of copper + 1/4 nugget of tin = 1 nugget of bronze" instead of "3 nugget of copper + 1 nuggets of tin = 4 nuggets of bronze" thing so the minimum quantity was effectively determined by which alloying recipes were in play
like, sure, i have 4 buckets in my bucket tank and that's an odd number of ingots, but eventually i will use all of those ingots and have one partial ingot in the bucket tank and one partial ingot in whatever tank i filled it from and i can either figure that out and recombine them, or void them.
actually, it did 3mb of copper + 1mb of tin
there was no relationship to nuggets there, it was just the absolute smallest sets that matched the ratio
I decided to at least make all recipes work with nuggets at minimum in newer versions, most notably salvage recipes tht scale the output by durability now clamp to nuggets
that worked in part because I could guarantee nuggets divide evenly into buckets
anyway i thought the point of having a grand unifying bucket quantity would be that even if something transfers stuff in units of ingots, every ninth ingot you have a bucket and that's a quantity that makes sense to everyone's pipes and everyone's tanks
ideally if everyone works with buckets in the end it all unifies
but its nice to be able to have a tank that has a capacity in terms of other units
maybe a bucket is 10 ingots, but why am I not allowed a tank that holds 64 ingots?
if I allow it, now we end up with some fun leftover fluids. Some mods can deal with that just fine, tinkers notably has ways to deal with it. But not every mod that uses fluids should be forced to come up with a solution like that
i guess
if i put my fluid that is only useful in 1/3 or 1/4 bucket bottles in your 64 ingot tank, then, sure, i get an odd fraction of a bottle left over, but the other fraction is still in whatever source tank i transferred it from
and i can recombine them if i want to use them in my recipes that only work in quantities of bottles
like, unless you've already voided part of your leftover fluids, all your partial units are somewhere.
and they still add up to the whole original amount you had
I dont really think this is a great argument, other ports by this author also just end up porting Forge tags to fabric as well (before the introduction of c tags to both loaders). Parity with the fabric ecosystem and changing values around is not a major concern of the mods ported to fabric with porting lib
My key argument there is using Tinkers to justify Fabric APIs is a little silly
Use a mod thats on Fabric that uses ingots
With the way the new handler is designed it would technically be possible to stack fluids in the same way that items are stacked, with different stack sizes for each liquid, because the getLimit method accepts an index and a resource
if you really wanted to .getLimit(0, water) and getLimit(0, lava) could return different results
but unless Neo standardizes that sort of concept in the fluid API its not happening
Some sort of getBucketCapacity() method on fluid type
Though not sure I want to try dealing with fluid container items in a world where bucket capacity varies per fluid
something as simple as "stores half a bucket" breaks as soon as someone has an odd number for their units
yeah, that would be real annoying, the bucket stuff currently all makes the assumption that 1 bucket is always the same amount of liquid
just sayin that if you want to do it yourself on a storage level you can
I think I just keep becoming more convinced we won't find something better than millibuckets
either we get nice divisions or the APIs working with the fluids are conveient
well no, millibuckets suck, i just dont think people will agree on whats better enough to actually change it
or we need to get all modders to agree on a value together...
never said they don't suck, just a lot of these options so far suck more 😛
if nothing else, millibuckets have momentum, we know how to deal with them
i mean, latvian just suggested we use 162k 
your issues with 81k for capacity reasons go away if the storage was switched to using longs :P
maybe what we really need is to build out that display system (so we can do 1/1296000 or whatever without people having to actually see 648000/1296000 unless they actually have a quantity that isn't divisible by anything, and to allow fluids to specify that they can display in units like nuggets or whatever)
the bigger it is, the harder it is for a human to think about how two different units relate
and that is the only point of a bigger number, different units
I'm not even convinced a fluid display system solves this entirely, since transfer will mess with it
frankly i think displaying whatever is the smallest unit [bottle, nugget, etc] as a decimal is probably fine for most purposes
plus, why do these numbers have so many 0s at the end?
I never heard a good argument for why 81000 instead of 8100
never seen a mod that actually needed mb, we just all are used to it
keeping a factor of 1000 is as bad as keeping mb
8100 is not divisible by 8
do people use 8?
it got 5 votes
being divisible by 8 is fine, but he's right, there's no good reason as far as i know to be divisible by 125.
i think in general being divisible by the factors of 4 is helpful for minecraft stuff
I use a lot of factors just because they exist rather than because I actually need them
some of them can easily be moved to a related factor
e.g. back in 1.16 I even had divided a nugget into 8 pieces at one point
upon porting and discovering that ws the last holdout for 16mb nuggets I just decided it was a stupid choice back then and ditched it
iirc 5 ingots crafts 64 grinding balls in enderio - a grinding ball cast might therefore use 45/64 of a nugget.
6 ingots makes 16 iron bars
I just round it to some number of nuggets
means recycling is lossy from the crafting table, but you get a discount for casting it
by who?
if those votes were from random people who dont actually do any fluid work, then their vote is kinda pointless
that is the biggest problem with this poll, its so anonymous we cannot ask "why"
yeah. but grunt, mr maintainer over here, can we make a seperate brainstorming channel for this
I feel like to get useful data, we need to ask factors + why. I want 8 because "blah blah" has 8 units