Add inventory swapItems() method as a workaround for lack of atomic list()+pushItems()
umnikos opened this issue ยท 5 comments
Say you want to transfer only diamonds from an ender storage chest on top to a regular chest on the bottom. Simple enough:
local chest = peripheral.wrap("top")
local slot = find_diamonds(chest.list())
if slot then
chest.pushItems("bottom",slot)
end
Exceeept that doesn't always work. There is a small amount of time between the .list()
call and the .pushItems()
call, so if the diamonds were replaced with dirt in the meantime, that would now mean there's dirt in the bottom chest, which would be bad. Thankfully we can use an intermediate storage device to act as a buffer; Get an item, look at what you've got and if it's not what you wanted return it back. Simple enough:
local chest = peripheral.wrap("top")
local slot = find_diamonds(chest.list())
if slot then
chest.pushItems("right",slot)
local buffer = peripheral.wrap("right")
local is_diamonds = find_diamonds(buffer.list())
if is_diamonds then
-- let the diamonds through
buffer.pushItems("bottom",is_diamonds)
else
-- return junk back as to not clog the buffer
buffer.pushItems("top",is_diamonds)
end
end
Problem is, this doesn't work either! The place where this can fail is in the "return junk back" step, as the chest we took it from might now be full and thus there's no space for it. We could keep it in the buffer until the top chest is not full, but if multiple of these failures compound eventually the buffer would be completely full as well and the system will deadlock. Here's the proposed fix for this conundrum: a .swapItems()
method:
local chest = peripheral.wrap("top")
local slot = find_diamonds(chest.list())
if slot then
-- will always succeed even if the buffer is full
chest.swapItems("right",slot,1) -- "limit" parameter doesn't make sense as it's just swapping the contents of the slots
local buffer = peripheral.wrap("right")
local is_diamonds = find_diamonds(buffer.list())
if is_diamonds then
-- let the diamonds through
buffer.pushItems("bottom",is_diamonds)
else
-- no need to return anything
end
end
Now this will finally guarantee only diamonds get into the bottom chest and that the system will not deadlock itself.
An alternative fix to the same problem would be to implement some mutex method for storages so that .list()
and .pushItems()
can be done atomically, but this seems simpler to implement and more fun to use.
Thank you for the detailed writeup here!
Looking at your last example, I'm not entirely convinced that swapItems
function is the right solution there though. This means your buffer chest holds onto that non-diamond item until the next swap, which will not be ideal in a lot of cases!
I feel a better solution here is to have a way to filter what item pullItems
/pushItems
will move - enabling something like compare and swap.
That said, in your specific case, you can just borrow a trick from vanilla item filters - keep one diamond in the bottom chest, and make pushItems
target that one slot. That guarantees that only diamonds can be moved.
Buffer unnecessarily keeping items can be fixed by just returning whenever possible
local chest = peripheral.wrap("top")
local slot = find_diamonds(chest.list())
if slot then
chest.swapItems("right",slot,1)
local buffer = peripheral.wrap("right")
local is_diamonds = find_diamonds(buffer.list())
if is_diamonds then
buffer.pushItems("bottom",is_diamonds)
else
-- return the item if possible
buffer.pushItems("top",1)
end
end
The vanilla item sorter trick only works on stackable items which is not all items you might want to sort in a storage system.
I can't dispute the usefulness of a dedicated filter parameter on pushItems
/pullItems
but I worry that depending on the way it gets implemented it might be inflexible; if it accepts an item name then sorting shulker boxes through nbt data is not possible. It's probably way more user friendly than my proposed solution, though.
Another thing that is possible is to implement storage
API similar to fluid_storage
logic, rather that slot-based :)
@SquidDev Sorry for the nudge; Has any work been done on filter parameters for pushItems
/pullItems
? After some thinking I have realized that every practical use case is covered by being able to filter on item name and nbt hash.