Request: more granular inserter control
Posted: Fri Mar 18, 2016 5:43 am
Earlier, I was trying to create a "prioritizing smart inserter" -- that is, based on the order of the filters, the inserter would remove items from an inventory with a preference for items matching the first/left-most filter, and if none of those items were available, it would prefer items matching the second filter, etc.
However, the two solutions I found were incredibly error-prone and "kludge-y".
The first was to try to determine the number of ticks a full cycle would take (ignoring, for a moment, the very real possibility of the inserter, in the addition to having to rotate, also has to extend) based of the entity's rotation_speed value. However, even under this simplified scenario, it would sometimes drop off items 1-2 ticks after it should (slower), and other times drop off and even pick up the next stack of items 1-2 ticks early. So while this was somewhat feasible for slow inserters, anything near the speed of smart inserters eventually gets caught up. (By the way, the idea was to save the filter list, then right before it returned to the source inventory, temporarily clear the filter list so that nothing would be picked up, wait a few more ticks for the arm to fully come to rest, then re-enable a specific filter, based on what was available in the source inventory. But on top of that, you really wanted to minimize the number of ticks that the filter list was anything other than what the user specified. For one reason, it would be confusing, and for another, any changes they made would be lost when the script restored the filters.)
The second attempt was to, every tick, peek at the inserter's hand. If this tick it has a stack, but last tick it didn't, we assume we're still hovering over the source inventory. And if the stack-in-hand is not the highest priority item available, swap the stack with an appropriate amount of the highest priority item.
In addition to seeming inefficient by having to peek every tick, this approach also gives rise to a difficult to solve corner case: What do you do if you can't actually re-insert the items into the chest because the space you freed has since been taken up (e.g. by other inserters inserting to top off the stack or insert a different item into the slot)? My half-solution was/is to continually try to push those backlogged items back into the chest, and if the inserter is mined before it has a chance to empty its backlog, spill the backlog stack onto the ground.
But this code could be made so much cleaner, and allow for modders to add more logic to inserters.
Interface additions that would help:
-- In addition to .active, which stops the inserter dead in its tracks, give inserters a .working member (named however you want), that simply prevents new stacks from being picked up, but allows the inserter to complete it's current delivery cycle, if it's on one. This way, As soon as I noticed that the inserter had an item in it's hand, I could toggle the .working flag. It would still complete the current delivery, and then return to its resting position above the source inventory, but not pick up an item. Later, without being required to watch every tick, I could revisit the inserter, check the inventory for the highest priority item, manually move the the stack from the inventory to the inserter's hand, and re-set the .working flag. And since the inserter was not working when it returned to the source chest, it never picked up an erroneous itemstack from it, that I had to figure out how to replace.
-- Allow inserters to be "tagged" to emit an event when they drop-off / return / pick-up. I think it would be serious overkill to have all inserters emit events, so if it's an all or nothing scenario, I'd certainly lean towards having no inserters emit events than all. That being said, there is a distinct advantage in being able to just listen for specific events, rather than the tick event. The difference between the return and pick-up events are that the return event would be guaranteed to fire before the inserter picked up another itemstack, whereas pickup would fire after. If these events fired on the tick immediately before and after the "finish returning, pick-up new stack" events, that would be idea, but any time reasonably close would work too.allow for a 0-tick window where
-- Emit an event whenever the user opens or closes an Entity GUI (e.g. the filter/circuit network condition dialog on a smart inserter). The "open" event should fire before the data used to populate it is finalized. And the "close" event should fire after the user closes the event, so that the script can act accordingly as soon as possible, instead of constantly checking on_tick. The contract here would be, provided they occur on or before the tick that the callback receives the event, any changes made to the filter settings or circuit network conditions would be reflected by the GUI when it was rendered. This is kind of a workaround compared to the other options, but it would allow me to do the filter juggling I described above, but make the juggling transparent to the user. In addition, any changes the user made to the filters or circuit conditions while the inserter had been temporarily modified can be applied ASAP, and not overwritten.
However, the two solutions I found were incredibly error-prone and "kludge-y".
The first was to try to determine the number of ticks a full cycle would take (ignoring, for a moment, the very real possibility of the inserter, in the addition to having to rotate, also has to extend) based of the entity's rotation_speed value. However, even under this simplified scenario, it would sometimes drop off items 1-2 ticks after it should (slower), and other times drop off and even pick up the next stack of items 1-2 ticks early. So while this was somewhat feasible for slow inserters, anything near the speed of smart inserters eventually gets caught up. (By the way, the idea was to save the filter list, then right before it returned to the source inventory, temporarily clear the filter list so that nothing would be picked up, wait a few more ticks for the arm to fully come to rest, then re-enable a specific filter, based on what was available in the source inventory. But on top of that, you really wanted to minimize the number of ticks that the filter list was anything other than what the user specified. For one reason, it would be confusing, and for another, any changes they made would be lost when the script restored the filters.)
The second attempt was to, every tick, peek at the inserter's hand. If this tick it has a stack, but last tick it didn't, we assume we're still hovering over the source inventory. And if the stack-in-hand is not the highest priority item available, swap the stack with an appropriate amount of the highest priority item.
In addition to seeming inefficient by having to peek every tick, this approach also gives rise to a difficult to solve corner case: What do you do if you can't actually re-insert the items into the chest because the space you freed has since been taken up (e.g. by other inserters inserting to top off the stack or insert a different item into the slot)? My half-solution was/is to continually try to push those backlogged items back into the chest, and if the inserter is mined before it has a chance to empty its backlog, spill the backlog stack onto the ground.
But this code could be made so much cleaner, and allow for modders to add more logic to inserters.
Interface additions that would help:
-- In addition to .active, which stops the inserter dead in its tracks, give inserters a .working member (named however you want), that simply prevents new stacks from being picked up, but allows the inserter to complete it's current delivery cycle, if it's on one. This way, As soon as I noticed that the inserter had an item in it's hand, I could toggle the .working flag. It would still complete the current delivery, and then return to its resting position above the source inventory, but not pick up an item. Later, without being required to watch every tick, I could revisit the inserter, check the inventory for the highest priority item, manually move the the stack from the inventory to the inserter's hand, and re-set the .working flag. And since the inserter was not working when it returned to the source chest, it never picked up an erroneous itemstack from it, that I had to figure out how to replace.
-- Allow inserters to be "tagged" to emit an event when they drop-off / return / pick-up. I think it would be serious overkill to have all inserters emit events, so if it's an all or nothing scenario, I'd certainly lean towards having no inserters emit events than all. That being said, there is a distinct advantage in being able to just listen for specific events, rather than the tick event. The difference between the return and pick-up events are that the return event would be guaranteed to fire before the inserter picked up another itemstack, whereas pickup would fire after. If these events fired on the tick immediately before and after the "finish returning, pick-up new stack" events, that would be idea, but any time reasonably close would work too.allow for a 0-tick window where
-- Emit an event whenever the user opens or closes an Entity GUI (e.g. the filter/circuit network condition dialog on a smart inserter). The "open" event should fire before the data used to populate it is finalized. And the "close" event should fire after the user closes the event, so that the script can act accordingly as soon as possible, instead of constantly checking on_tick. The contract here would be, provided they occur on or before the tick that the callback receives the event, any changes made to the filter settings or circuit network conditions would be reflected by the GUI when it was rendered. This is kind of a workaround compared to the other options, but it would allow me to do the filter juggling I described above, but make the juggling transparent to the user. In addition, any changes the user made to the filters or circuit conditions while the inserter had been temporarily modified can be applied ASAP, and not overwritten.