3. Interacting with Components

Enough talk! Now that we have built a computer, know how it works and how the component network works, we can finally start with writing some code to interact with machines.

What we have done so far:

  • We built a computer case with a Lua CPU and a Memory T1 Module

  • We built a component network with one attached storage container

We want to write a program that prints the count of items of the storage container into the console output.
For this we first have to reference the storage container and then interact with to storage container.

Lua APIs

In the Lua runtime the CPU provides a couple of APIs and libraries that allow you to interact with the computer case and other internal systems.

These libraries are "tables" in "global variables" containing "static" functions. This means you can use these libraries and their functions everywhere in your program.

For now, we focus on the "component" library which allows us to interact with the computer component network.

Proxy Components

As already mentioned, the UUID of a component is a unique identifier like a MAC-Address of a network component and can be used to reference an individual component.

The component library provides a proxy function to get a Lua Reference of a component by its id.

local container = component.proxy("123")

When you replace the 123 in the code snippet above with the UUID of your storage container, you get a reference to it which is stored in the container variable.

Using the print function which got reimplemented by FIN you can output the variable to the console. It will convert the reference to descriptive string for easy debugging.

print(container)

For a component with id 123, nick test meep and type Computer you will get an output like: 123 "test meep" Computer.
For a component with id 123, no nick and type Computer you will get an output like: 123 Computer

The value that the proxy function returns is also referred as Object-, Component-, -Instance or -Reference.
These are special values created by FIN and not necessary Lua native.
It’s of type "userdata" which behaves similar to tables in Lua.
Each object type also has its own meta-table.
But that goes more in-depth, we will have a look at those unique things slowly as we progress.

If the proxy function can not find a network component with the given id, it will return nil.

The proxy function also allows you to do batch proxies. Meaning you can "resolve" multiple UUIDs at the same time to their references.
This can be done in multiple ways.

Instead of a single string you can also pass an array of strings to the proxy function, and it will return an array/list of references.

local arr = component.proxy({"123", "456", "789"})
print(arr[1], arr[2], arr[3]) -- 123 456 789 in that order

Additionally, the proxy function can take a variable amount of arguments and convert each argument separately. It will then return the converted values in the multiple return values in the same order as given in the parameter list.

local c1, a1, c2, a2, a3 = component.proxy("...", {...}, "...", {...}, {...})

Object Reference

Object references are also called instances, object instances or references.

These object references as already mentioned are a special data-type introduced with FIN of native-type "userdata". It behaves similar to a tables, that means you actually just store the reference of the data-type inside variables etc. In this case it means inside of Lua you actually work with a reference to a reference to the game object.
Sounds complicated, but it’s not, as the data type heavily utilizes Lua metatables to provide a simple interaction interface.

These references base upon the reflection system of FicsIt-Networks, and it heavily bases upon the system of the unreal engine.

Most (not all) "structures" inside unreal engine are objects. Objects represent a "thing" and hold different values, also called attributes or properties, and the have functions to interact with the object.
Also important to note is that each object has a "Type". You call the objects also "instances of a class". A class is nothing else as another object that describes a different object.
If you f.e. have a storage container and use the proxy function to get a reference to it. You reference an instance of type storage container ("storage container" is the type of the object, and the specific object with the ID you used in the proxy is a instance of it). The class (aka. type) "storage container" now describes what attributes and functions a storage container has. A constructor f.e. has a standby attribute that describes if the constructor is turned off or not, and it has a function setRecipe that allows you to change the recipe the constructor crafts.

Important to note is that it also uses a hierarchy of types to share common traits between types. A constructor f.e. shares the standby functionality with many other machines just like power generators but still adds its own functionality.
A constructor is of class/type "constructor" and adds functionality like changing the recipe it produces. The "constructor" class/type extends the "factory" class/type which adds f.e. standby functionality. A power generator is of class/type "power_generator" also extends the "factory" class/type and instead extends it with a "validFuel" function. This ensures you can reference a object, expect it to be of type "factory", and to you, it won’t matter if the actual type is a generator or constructor, you can still use the standby functionality because both are factories.

Functions have to be called as member functions, that means the first parameter of a function call is the reference that will be used in the call. Lua has some syntactical sugar to ease the use.

comp.funcToCall(comp, param1, param2, ...) -- this is how to call such function

comp:funcToCall(param1, param2, ...) -- when using a : instead of . you dont need the first parameter

When you need to do many calls of the same function (of the same type, not instance) you may want to only resolve the function name once to heavily optimize your runtime.

This is the case because resolving the function name is a bit heavy.

local func = comp1.funcToCall -- resolve the function name and store it in a var

func(comp1, p1, p2, ...) -- call it on the first component
func(comp2, p1, p2, ...) -- call it on the second component
func(compX, px, py, ...) -- call it on any further objects of that type

The reference to the object is not stored in the function value as "c closure" like in many other environments. Instead, it is stored in the actual object you use, and that is why you have to pass it to the function as you use it.

Every object bases upon the Object type/class. This class introduces a couple of attributes you need from time to time:

  • hash attribute and getHash() function
    The attribute is read only and the function returns the same value as the property provides. It returns an integer value that is a hash of the internal reference. You want to use this when you f.e. use a object as table key.
    Lua tables work by using the objects own instance, but as already discussed, we have a reference to a reference. That means when you do two separate proxy calls and get the same object (you used the same UUID), Lua will view them as individual values. And that means it can not be used as table key.
    You don’t need to use it for comparison operations like ==, because this functional can be overwritten using Lua metatables and FicsIt-Networks does that for you.

  • internalPath attribute
    This attribute is similar to the hash but instead it is a string and gives you the VFS path of the Unreal Engine object. It is useful for in-depth debugging or when you want to reference a UE object with another mod or inside the game console.

  • internalName attribute
    It is similar to internalPath but instead allows to access the internalName attribute of the UE object.

  • getType() function
    This function allows you to get an object that represents the type (not a individual instance) of an object.
    With it, you can f.e. dynamically discover attributes and functions, show their descriptions etc.
    We will cover how to use the reflection system inside Lua in another section.

There is also a special type of Instance called a Class Instance.
Class Instances are different from Objects of Type Class.
Objects of type class are objects that hold information for a given type, while Class Instances are like singletons.
We use Class Instances to represent in-game "object types" that hold information. f.e. an Item Type (Iron Plate), doesn’t have instanced, there are not many "Iron Plate Types", there is just one type of "Iron Plate". Tho, such a type holds information like a name, if you can discard the item or not, or how much energy it releases when burned. That means it behaves just like a object instance. This duality is separated from normal objects, objects of type class and class types, by making them their own type "class instances".

Properties can be used just like fields in a Lua table.
Tho many are read-only and Lua will throw an exception accordingly.
This behaviour is implemented by overwriting the index and new-index functions in the meta-table.

Referencing a component is a bit special.
Such reference will have additionally two more properties you can work with.
The additional readonly id field allows you to get the UUID of the component.
The additional nick field allows you to read and write the components nick. This especially useful when you use the nick as means of storing configuration.

Actually using this Information

Phew…​ that was exhausting.
This was some very useful information we want to understand to make the best out of our programs.
After all, interacting with machines is one of the key things we want to do.

Let’s finally write a program that prints the amount of items inside a storage container to the console.

For this we first have to get the reference to our storage container as we already have discussed.
Even tho we said its no good design to "hard-code" UUIDs into our code, we will do so for now because it is the simplest way of doing it.

local container = component.proxy("...")

Now we need to get a reference to the internal inventory object of the container. This inventory object type is used all over the place to store multiple stacks of items. Every object that exists inside the world and is not part of another world object, is called Actor (see the unreal engine reference). The Actor class provides a getInventories() function, that allows us to get all inventory components of the actor.
We exactly need this in our code to access the first (and only) inventory of the storage container, to get the count of items.

local inventory = container:getInventories()[1]

Now we want to print the item-count to the console. We could separate the part were we get the item-count from the inventory and the part where we print to the console, but since its not much code in one line we don’t do that now.

print("Item-Count:")

This will only print "Item-Count:" to the console. In an additional parameter of the print function we want to pass the item count we get from the container. We can get the item count from the inventory by using the inventories itemCount property.

inventory.itemCount

We should now have some code like:

local container = component.proxy("...")
print("Item-Count:", inventory.itemCount)

When we now hit the power button of the computer case, it will briefly run and then stop since we reached the end of our program.

We now should have a console output like Item-Count: 42.