5. Structs and Class Instances
Okay, our program already can count the items of multiple container.
Now we want to make it really fancy. We want to separate it by item-type.
For this we have to learn about structures.
Structures
Structures in FicsIt-Networks are essentially the same as Lua Tables. The structure system of FicsIt-Networks is not fully fleshed out, and you can expect some changes in the future.
When a structure is returned by a function or property, it won’t reference back to where it came from.
Imagine you have a object obj
and it has a struct property struct
that has a property field
.
If you now change the value of field
, it won’t change the struct inside the obj
.
print(obj.struct.field) -- prints "meep"
obj.struct.field = "nice"
print(obj.struct.field) -- prints "meep"
You can change it, when you explicitly reassign the struct
to the obj
.
print(obj.struct.field) -- prints "meep"
local struct = obj.struct
struct.field = "nice"
obj.struct = struct
print(obj.struct.field) -- prints "nice"
Structures that are returned can have methods you can call similar to instances, because they are special Lua types with metatable shenanigans.
When you want to pass struct to a function, you can either use a struct you got previously from a function that matches the type, or you create table that has at least fields with exact same internal name as the attributes shown for the structure type in the reflection. These fields also have to have values that can be converted implicitly to the type as demanded in the reflection.
f.e. a Vector structure consists of a x
, y
and z
float attribute.
Imagine a function setPosition
that expects a Vector as first parameter,
then your code can look like that (multiple versions):
setPosition({x: 1, y: 2, z: 3})
local pos1 = {x: 1, y: 2, z: 3}
setPosition(pos1)
local pos2 = {}
pos.x = 1
pos.y = 2
pos.z = 3
setPosition(pos2)
local pos3 = {}
pos3["x"] = 1
pos3["y"] = 2
pos3["z"] = 3
setPosition(pos3)
If an attribute is missing from your passed structure, the system will try to use a default value for the missing attribute if possible.
Field in the table that are not defined by the reflection system will simply be ignored.
Fancy Item Counter
The section is called struct and class instances not because we gonna talk about them, we did that already in "Interacting with Components", instead this will be the first time when we will properly have to use them.
We will adapt our item-count code to the new way we want to count item based on the item types.
For this we have to figure out what data type we want to use, for our sum variable that should hold the end result.
My proposal is a table, with the time-type as key and the count integer as value.
local sum = {}
Next, inside our for loop body, we want to iterate over all slots
of the storage containers inventory.
Using the getStack(…)
function we can get the item-stack at a given slot
based on the slots index.
Okay, we can now get individual stacks from an inventory component,
be before that we need to know how many slots there actually are.
For this we can use the size
property of the inventory component.
That means in for loop body, that iterates of the container,
we need to add another for-loop that iterates the slot indexes.
for slotIndex = 0, inventory.size - 1, 1 do
...
end
We need to subtract 1 from the inventory size since the for loop iterator iterates inclusive and the slot indexes start at 0 (As most programming languages do. This is an artefact since this is a reflected function and the reflection system is not Lua specific).
In this second for-loop we now want to get the stack from the inventory based on the iterated slot index.
local stack = inventory:getStack(slotIndex)
The getStack(…)
function is a variadic function and takes multiple slot indexes,
and returns the corresponding the stack-structures.
Such an item-stack structure has a count
field that tells us
how many items there are in the stack.
And it has a item
field is a item-struct that tells us what item is stored in this slot.
The item
field is a struct because "a item" does not only consist of the type,
but also additional metadata (item-state) that could be accesses.
FicsIt-Networks currently doesn’t provide a way to access this metadata,
but when that happens it can be stored and accessed using this struct.
This item-struct has a field "type" which has a "item-type" class instance.
We can use this class instance to differentiate the different types of items. And so, this is what we want to store as key.
That means in our code we now want to get the current item count of the item type,
add the item-count
of the stack to it, and store it again in the map.
For this we also need make sure an entry actually exists for the item,
because when there is a new item we never put into the map, we need to create
a map entry, or at least our calculation has to account for that.
First we need to get the item-type of our stack.
local type = stack.item.type
If a slot is empty, you still can access the structs, because structs "can’t be nil".
Instead, the type field will be set to nil.
So we want to make sure our counting code only runs if we actually have a time, otherwise using it for the map key won’t work.
So we simply wrap it in an if.
if type then
...
end
In the if we need to get the map entry of that type (if it exists).
local itemCount = sum[type.hash]
Notice the usage of the types |
Next we make sure itemCount
is a valid number we can calculate with.
itemCount = itemCount or 0
The above code is neat syntax possible with Lua where the or
operator
can be used for non-boolean values.
It checks the first value if it is implicitly false (the case with nil
or 0)
and if that is the case, returns the other value, otherwise it returns the first value.
Now we simply add our stack item count to the itemCount
.
itemCount = itemCount + stack.count
Followed by setting/adding the map entry to the new itemCount.
sum[type.hash] = itemCount
With this we should have our "counting logic".
Last thing we need to do, is to iterate over our map,
and print the info to the console.
For this we first use the Lua pair
function and a for each loop
to iterate over the map.
for typeHash, count in pairs(sum) do
...
end
In the body we use the all well known print
Function to print some text and the count.
print("Item " .. count .. "x")
This code works, but it doesn’t really help, because we don’t
know how many items there of a given type.
For this we need to make second map, where we store the actual class instance
as value and its hash as key.
local types = {}
And were we change the item-count map entry, we simply change/create the types map entry to the actual type.
types[type.hash] = type
Now we just need to additionally print the name of the type associated with the
with item-count entry in the sum map.
We can rely on the hash for this, since the keys in the two maps are the same.
TO get the name of the type, we simply can now access the name
field of the item-types class instance.
print("Item '" .. types[typeHash].name .. "' " .. count .. "x")
So our end-result should look something like:
local containerIDs = component.findComponent(findClass("FGBuildableStorage"))
local containers = component.proxy(containerIDs)
local sum = {}
local types = {}
for _, container in ipairs(containers) do
local inventory = container:getInventories()[1]
for slotIndex = 0, inventory.size - 1, 1 do
local stack = inventory:getStack(slotIndex)
local type = stack.item.type
if type then
local itemCount = sum[type.hash]
itemCount = itemCount or 0
itemCount = itemCount + stack.count
sum[type.hash] = itemCount
types[type.hash] = type
end
end
end
for typeHash, count in pairs(sum) do
print("Item \"" .. types[typeHash].name .. "\" " .. count .. "x")
end
If we run this code now, we should be able to check our storage of items in the computer console easily.
Be aware since this is already a fair bit of code, there are things you could do differently to get the same. It is also not the most optimized code, but its good enough for us.