LuaInventory::get_filtered_slots()

Place to ask discuss and request the modding support of Factorio. Don't request mods here.
Post Reply
Pi-C
Smart Inserter
Smart Inserter
Posts: 1655
Joined: Sun Oct 14, 2018 8:13 am
Contact:

LuaInventory::get_filtered_slots()

Post by Pi-C »

What?
I'd like to get a method to get the indices of all filtered slots in an inventory:

LuaInventory::get_filtered_slots() --> Array of uint
Why?
We already have LuaInventory::is_filtered(), which returns true if the inventory supports filters and has at least one filter set, and LuaInventory:get_filter(index), which will get the filter of a certain slot. Checking for LuaInventory::is_filtered() allows me to skip looking for filtered inventory slots if the inventory has no filters set. But once I know that the inventory has filters, I must use something like this to find the slots that have a filter:

Code: Select all

if inventory.is_filtered() then
	for slot, #inventory do
		if inventory.get_filter(slot) then
			-- Do stuff	
		end
	end
end
A method LuaInventory::get_filtered_slots() which returns an array of integers corresponding to the indices of filtered slots would allow me to process all filtered slots without looping over the complete inventory:

Code: Select all

if inventory.is_filtered() then
	local filtered = inventory.get_filtered_slots()
	for s, slot in ipairs(filtered) do
		if inventory.get_filter(slot) then
			-- Do stuff	
		end
	end
end
Use case:
Autodrive allows car-based vehicles equipped with a "logistic sensor" to autotrash and request items in their trunk. When such a vehicle is standing within an area covered by a roboport, it will create a hidden requester and a hidden active-provider chest. It then will scan the inventory, looking for special items like armors or blueprints (can only be provided) and common items. Slots filtered for common items will be counted as requests if stack.count < LuaItemPrototype::stack_size, while the contents of unfiltered slots will be provided.

On each turn, we process 10 trunk slots: We set filters in the hidden requester chest if any requested items are still missing, and move provided items to the active provider chest, if there is any space left. This is repeated until the entire inventory has been scanned. On the start of the next turn, all items that have been delivered to the hidden requester will be inserted back into the trunk (where they will automatically fill the filtered slots), all tables will be reset, and we start over scanning the inventory from slot 1.

Now suppose the inventory looks like this:
car-trunk_with_requests_and_provides.png
car-trunk_with_requests_and_provides.png (18.07 KiB) Viewed 879 times
In the latest version of Autodrive, the hidden requester chest would be set to request 400 copper plates, and 5 stacks of copper plates would be transferred to the hidden active-provider chest. The bots would then move 400 of the copper plates from the provider into the requester chest and clear out the remaining 100 copper plates from the provider chest. However, it would be more efficient if I'd directly move 4 of the 5 provided stacks to the filtered slots, without involving any bots.

In my WIP version, I've taken a slightly different approach: On each turn, I check for each unfiltered slot with common items whether the item is also on the list of requested items. If it is, I try to insert the stack into the trunk (will go to a filtered slot), reduce the amount of requested items, as well as the amount of items in the unfiltered slot. Only the remaining provided items are then moved to the active provider. However, this still isn't satisfying. With the trunk inventory above, the following would happen:
  • Turn 1: Two stacks of copper-plates are found. As there are no requests yet, they are moved to the active provider,
  • Turn 2: Four empty stacks filtered for copper-plates are found, as well as one stack of copper-plates. This will be moved to the first filtered slot, leaving three slots for which filters will be added in the requester.
  • Turn 3: Two more stacks of copper-plates are found. They should be moved to the next two filtered slots, and two requests should be removed from the requester chest.
Thus, although there are 5 unfiltered vs. 4 filtered slots to begin with, not all requests will be fulfilled directly from the provided stacks. On the other hand, if something like the suggested LuaInventory::get_filtered_slots() was available, I could use the list of filtered slots to get all requests on the first turn, and move any stacks that provide a requested item directly to the filtered slots before I'd begin to request or provide items via the hidden chests. It would also be easy to choose other approaches based depending on the number of filtered slots returned (e.g. don't loop over the complete inventory on the first pass if the number of filtered slots is the same as the number of slots in the inventory).
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!


Pi-C
Smart Inserter
Smart Inserter
Posts: 1655
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: LuaInventory::get_filtered_slots()

Post by Pi-C »

This has the disadvantage that it will sort and merge the complete inventory, which may be a bit obtrusive. For example, as items are provided from left to right, beginning at the first and ending at the last line, players may have put the items they want to get rid of first somewhere at the top, but sorting might place them at the bottom. If the car then starts moving again before the bots have had a chance to carry off all other items, the vehicle will still have the items the player wanted to dump.
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!

curiosity
Filter Inserter
Filter Inserter
Posts: 330
Joined: Wed Sep 11, 2019 4:13 pm
Contact:

Re: LuaInventory::get_filtered_slots()

Post by curiosity »

FWIW, you can still do your thing in one pass. You just have to check the chests and manually move the trashed items if they can satisfy your request. E.g. in your example you would trash 200 copper in one tick, then in the next you would bring them back into the filtered slots and request 100 more, then in the third tick you remove the request and trash the remaining 100.

You could also optimize your code.

I'm pretty sure the game doesn't have an array of filtered slots ready. It will just be doing the exact same thing as you, going over the inventory slots to see if any of them are filtered. And then you have to iterate over the resulting array anyway.

Pi-C
Smart Inserter
Smart Inserter
Posts: 1655
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: LuaInventory::get_filtered_slots()

Post by Pi-C »

curiosity wrote:
Sat Jan 06, 2024 3:30 am
FWIW, you can still do your thing in one pass. You just have to check the chests and manually move the trashed items if they can satisfy your request. E.g. in your example you would trash 200 copper in one tick, then in the next you would bring them back into the filtered slots and request 100 more, then in the third tick you remove the request and trash the remaining 100.
That's a good idea! But I've a question regarding the bots' job queue: Do they get assigned to pick up items from a certain logistic chest, or from a particular slot in the its inventory? In the first case, they'd still get the item if there are several stacks of it, but in the other one, they'd return when the stack is removed …
You could also optimize your code.
Definitely! There seems to be a problem with the changes I've made yesterday, resulting in a huge UPS drop. I'll see how to fix that … :mrgreen:
I'm pretty sure the game doesn't have an array of filtered slots ready. It will just be doing the exact same thing as you, going over the inventory slots to see if any of them are filtered. And then you have to iterate over the resulting array anyway.
Agreed, but I suppose doing that in C would be faster than in Lua. That could be wrong, but I thought I'd leave that decision to the devs.
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!

curiosity
Filter Inserter
Filter Inserter
Posts: 330
Joined: Wed Sep 11, 2019 4:13 pm
Contact:

Re: LuaInventory::get_filtered_slots()

Post by curiosity »

Pi-C wrote:
Sat Jan 06, 2024 8:01 am
That's a good idea! But I've a question regarding the bots' job queue: Do they get assigned to pick up items from a certain logistic chest, or from a particular slot in the its inventory? In the first case, they'd still get the item if there are several stacks of it, but in the other one, they'd return when the stack is removed …
They don't know about slots, AFAICT.
Pi-C wrote:
Sat Jan 06, 2024 8:01 am
Agreed, but I suppose doing that in C would be faster than in Lua. That could be wrong, but I thought I'd leave that decision to the devs.
You still have to make the API calls to get the filters, so I doubt it would be any better. Now, if it returned the filters instead of just indices, that's more debatable.

Pi-C
Smart Inserter
Smart Inserter
Posts: 1655
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: LuaInventory::get_filtered_slots()

Post by Pi-C »

curiosity wrote:
Sat Jan 06, 2024 2:11 pm
Pi-C wrote:
Sat Jan 06, 2024 8:01 am
That's a good idea! But I've a question regarding the bots' job queue: Do they get assigned to pick up items from a certain logistic chest, or from a particular slot in the its inventory? In the first case, they'd still get the item if there are several stacks of it, but in the other one, they'd return when the stack is removed …
They don't know about slots, AFAICT.
Great!
Pi-C wrote:
Sat Jan 06, 2024 8:01 am
Agreed, but I suppose doing that in C would be faster than in Lua. That could be wrong, but I thought I'd leave that decision to the devs.
You still have to make the API calls to get the filters, so I doubt it would be any better. Now, if it returned the filters instead of just indices, that's more debatable.
[/quote]
Without the indices, I'd still have to loop over the inventory to find the filtered slots. But you're right, getting the filters would be useful as well! So perhaps the method should return an array of "{ filter = string, index = uint }", or even simply an array of "{ string, uint }" instead?
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!

curiosity
Filter Inserter
Filter Inserter
Posts: 330
Joined: Wed Sep 11, 2019 4:13 pm
Contact:

Re: LuaInventory::get_filtered_slots()

Post by curiosity »

Pi-C wrote:
Sat Jan 06, 2024 7:51 pm
So perhaps the method should return an array of "{ filter = string, index = uint }", or even simply an array of "{ string, uint }" instead?
Why do you want it to be an array so much?

Pi-C
Smart Inserter
Smart Inserter
Posts: 1655
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: LuaInventory::get_filtered_slots()

Post by Pi-C »

curiosity wrote:
Sat Jan 06, 2024 10:08 pm
Pi-C wrote:
Sat Jan 06, 2024 7:51 pm
So perhaps the method should return an array of "{ filter = string, index = uint }", or even simply an array of "{ string, uint }" instead?
Why do you want it to be an array so much?
Sorry if I didn't get that across correctly: just like LuaSurface::find_entities() returns an array containing all the entities found, I want to get filter+index of all filtered slots in an inventory. So what I really want is an array of tables (or an array of arrays in the more simple case). This would make it easy to jump to each filtered slot directly. Suppose inventory.get_filtered_slots() returned {{"copper-plate", 2}, {"iron-plate", 24}, {"copper-plate", 33}, {"coal", 78}} for the trunk of a vanilla car, it would be sufficient to check just the stacks in these 4 slots rather than looping over all the 80 slots in the inventory.
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!

Pi-C
Smart Inserter
Smart Inserter
Posts: 1655
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: LuaInventory::get_filtered_slots()

Post by Pi-C »

Just back in from a nightly winter walk, where I thought about it some more.

Getting a list like { {filter = string, slot = uint}, {filter = string, slot = uint}, … } should be easy to implement (the code in my explanations is the Lua equivalent of what should be done in C):

Code: Select all

-- Returns for example:  { {"copper-plate", 1}, {"iron-plate", 5}, {"iron-plate", 6}, {"iron-plate", 7}, {"copper-plate", 34},  {"copper-plate", 55} }
function get_filtered_slots(inventory)
	local ret = {}
	if inventory and inventory.valid and inventory.is_filtered() then
		local filter
		for s = 1, #inventory do
			filter = inventory.get_filter(s)
			if filter then
				table.insert(ret, {filter = filter, slot = s})
			end
		end
	end
	return ret
end
The beauty of this is that this will also give you the number of filtered slots if you use '#returned_list'.


Another idea is to return a dictionary indexed by filter name, containing the indices of the slots with that filter:

Code: Select all

-- Returns for example:  { ["copper-plate"] = {1, 34, 55}, ["iron-plate"] = {5, 6, 7} }
function get_filtered_slots(inventory)
	local ret = {}
	if inventory and inventory.valid and inventory.is_filtered() then
		local filter
		for s = 1, #inventory do
			filter = inventory.get_filter(s)
			if filter then
				ret[filter] = ret[filter] or {}
				table.insert(ret.filter, s})
			end
		end
	end
	return ret
end
This makes it easy to look up all slots filtered for a certain item, but it's harder to get the number of filtered slots. However, one could add a counter to the list:

Code: Select all

-- Returns for example:  { 6, ["copper-plate"] = {1, 34, 55}, ["iron-plate"] = {5, 6, 7} }
function get_filtered_slots(inventory)
 	…
		local cnt = 0
		for s = 1, #inventory do
			filter = inventory.get_filter(s)
			if filter then
				ret[filter] = ret[filter] or {}
				table.insert(ret.filter, s})
				cnt = cnt + 1
			end
		end
		ret[1] = cnt
	…
end
Of course, it would be even more convenient if we'd also get the count of items in those slots, which means the game would have to check stack.is_valid_for_read for each slot.

Code: Select all

-- Returns for example:  { {filter = "copper-plate", slot = 1, count = 123}, {filter = "iron-plate", slot = 5}, …}
function get_filtered_slots(inventory)
	…
		for s = 1, #inventory do
			filter = inventory.get_filter(s)
			if filter then
				table.insert(ret, {filter = filter, slot = s, count = inventory[s].valid_for_read and inventory[s].count or nil})
			end
		end
	…
end
But I'm not sure whether that's worth the trouble. :-)
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!

curiosity
Filter Inserter
Filter Inserter
Posts: 330
Joined: Wed Sep 11, 2019 4:13 pm
Contact:

Re: LuaInventory::get_filtered_slots()

Post by curiosity »

Pi-C wrote:
Sun Jan 07, 2024 12:21 am
The beauty of this is that this will also give you the number of filtered slots if you use '#returned_list'.
What would you want it for?

Pi-C
Smart Inserter
Smart Inserter
Posts: 1655
Joined: Sun Oct 14, 2018 8:13 am
Contact:

Re: LuaInventory::get_filtered_slots()

Post by Pi-C »

curiosity wrote:
Sun Jan 07, 2024 1:58 am
Pi-C wrote:
Sun Jan 07, 2024 12:21 am
The beauty of this is that this will also give you the number of filtered slots if you use '#returned_list'.
What would you want it for?
Cf. the last sentence of my original post:
Pi-C wrote:
Fri Jan 05, 2024 8:42 pm
It would also be easy to choose other approaches based depending on the number of filtered slots returned (e.g. don't loop over the complete inventory on the first pass if the number of filtered slots is the same as the number of slots in the inventory).
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!

Post Reply

Return to “Modding interface requests”