Jump to content

A word about PostInit functions (AddPrefabPostInit, etc.) and more


Recommended Posts

I see a lot of people on here who seem to have trouble understanding the concept of exactly how PostInit functions work

Before I get into it, let me explain one key programming concept: references.

In lua, all functions are what are known as "anonymous" functions. That is, they don't have a specific name or identifier associated with them. When you create a function like this:

local function SomeFunction()  ...blahblah...end

In lua, it's just shorthand for this:

local SomeFunction = function() ...blahblah... end

You might notice how similar that looks to creating a variable:

local SomeVariable = "Look ma', I'm a string!"

There's a reason for this:  they're both variable definitions!
 
What is going on behind the scenes is lua takes the data after the = sign and stores it somewhere.  It then stores the address of where that data is inside your variable.  You can think of lua as being a parking valet service.  You give lua your car, and they give you back a slip of paper that informs them which car is yours when you go back to use it.
 
But wait! ..you may say, why does make the function anonymous? Doesn't that still mean that the name of the function is SomeFunction? nope. Here's why:

local function SomeFunction() --creates the "SomeFunction" variable and points it at our new function  print("I'm some function!")endSomeFunction() --let's try executing our function--output:  "I'm some function!"local TheSameFunction = SomeFunction --creates a new variable and tells it to point at the same functionSomeFunction()--output: "I'm some function!"TheSameFunction()--output: "I'm some function!"SomeFunction = nil --let's erase the variable that was pointing to our functionTheSameFunction() -- lets see what happens now--output:  "I'm some function!"--It still works!  That's because "SomeFunction" was just a variable that was--used to hold the address of our function, not the function itself.

Now, if you were to set TheSameFunction = nil at the end there, you would have no way to access your function, but it would still exist, technically, until the lua garbage collector came along and saw that there was nothing left pointing at it. (If you've heard of the term or experienced a "memory leak" before, this is one of the two main causes-- data "hanging around" because there are variables(or some other "thing") still pointing to it.  Fortunately for us, lua is very good at garbage collecting unused data :-))
 
Variables that contain the address of data rather than the actual data itself are references.  In lua, all functions are anonymous, and all variables are references. When you pass a function as an argument, you are really just passing a variable that happens to reference a function.  lua doesn't know if a variable's data is a function, a string, a number, or a taco truck until you ask it to use that data somehow.
 
OK! That may or may not have been necessary to understanding this next bit, but in my opinion it helps to form a stronger mental image of how everything hooks together. Moving on!
 
All of the PostInit functions (AddPrefabPostInit, for example) work more or less like this:

Add______PostInit("<type of _______>", <function to pass each instance of ______ into>)
 
Note that some PostInit functions don't require the first argument, and only accept a function.
 
With that in mind, let's look at a snippet of an example of how AddPrefabPostInit might be used:

function add_my_tag(inst)  inst:AddTag("mytag")endAddPrefabPostInit("smallbird", add_my_tag)

Here we are creating a function add_my_tag (remember that this is really just a variable that points at our new function!)
Below it, we are calling the function AddPrefabPostInit, and passing it two arguments: the string "smallbird", and our add_my_tag variable, which is a reference to our function that adds the "mytag" tag.
 
So what is AddPrefabPostInit doing with the string and the function reference? You could pop open modutil.lua and have a look-see, but I'll try to boil it down to its essence here.

For each mod, the game creates the table "postinitfns" that stores all functions you register this way. Here's the bit of code that the prefab-specific PostInit function that our example uses:

modutil.lua:

    env.postinitfns.PrefabPostInit = {} -- Note that it's creating a new table here    env.AddPrefabPostInit = function(prefab, fn)        initprint("AddPrefabPostInit", prefab)        if env.postinitfns.PrefabPostInit[prefab] == nil then            env.postinitfns.PrefabPostInit[prefab] = {}        end        table.insert(env.postinitfns.PrefabPostInit[prefab], fn)    end

(It's probably worth noting that "env" is your mod's environment. That is to say, it is the root table that contains all other variables that are accessible to, or created by, your mod. When you create a local variable inside your mod, you're actually creating env.variable. When you use "GLOBAL" in your mod, GLOBAL is actually the global environment. That's why you can't use certain variables without specifying GLOBAL. For example, when you try to use GetPlayer() inside your mod without specifying GLOBAL.GetPlayer(), lua tries to find it at env.GetPlayer(), which doesn't exist.)*
 
*this is the mildly simplified version.


Anyway, you can see what's going on easily in a side-by-side comparison of the AddPrefabPostInit function definition and our example's usage of the function:

AddPrefabPostInit = function(prefab, fn)AddPrefabPostInit("smallbird", add_my_tag)

"smallbird" is being passed to the prefab parameter.
add_my_tag is being passed to the fn parameter.
 
 
First, the function does this:


        if env.postinitfns.PrefabPostInit[prefab] == nil then            env.postinitfns.PrefabPostInit[prefab] = {}        end

In our example, that would look like this in action:

        if env.postinitfns.PrefabPostInit["smallbird"] == nil then            env.postinitfns.PrefabPostInit["smallbird"] = {}        end

The string "smallbird" is being used as an index to the env.postinitfns.PrefabPostInit table. This could also be written as:

        if env.postinitfns.PrefabPostInit.smallbird == nil then            env.postinitfns.PrefabPostInit.smallbird = {}        end

Essentially, the game checks to see if there's a "smallbird" index in the postinitfns.PrefabPostInit table. If not, it creates a new table at that index.  
 
Here's the line that actually registers our function:

table.insert(env.postinitfns.PrefabPostInit[prefab], fn)

See what's happening there? table.insert() does exactly what you'd imagine it to do-- it takes a variable and adds it to the table specified.   Here is what it would look like in action:

table.insert(env.postinitfns.PrefabPostInit["smallbird"], add_my_tag)

table.insert() takes the table contained inside PrefabPostInit["smallbird"], and appends to it a new reference to the the same function referenced by the variable add_my_tag.  Again, this could also be written:

table.insert(env.postinitfns.PrefabPostInit.smallbird, add_my_tag)

Every time the game creates an instance of a smallbird prefab, after it finishes the base game's initialization, it checks to see if there is a table at PrefabPostInit["smallbird"]. If there is, it goes through the table, one entry at a time, and passes the instance of the prefab into each function registered there. You can sort-of think of it as the game dipping the prefab into a series of pots, each one changing it some way. (It doesn't necessarily have to change it, but that is really what the PostInit functions are designed for.)

Each of the PostInit functions defined in modutil.lua work in a very similar manner:
Line 182: env.AddStategraphPostInit = function(stategraph, fn)
Line 192: env.AddComponentPostInit = function(component, fn)
Line 211: env.AddPlayerPostInit = function(fn)
Line 218: env.AddPrefabPostInit = function(prefab, fn)
Line 227: env.AddGamePostInit = function(fn)
Line 233: env.AddSimPostInit = function(fn)
Line 240: env.AddBrainPostInit = function(brain, fn)
 
Each of the PostInit functions that only accept one argument do so because there aren't multiple "types" of that object.

 

 

Edit:  minor typos

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

Please be aware that the content of this thread may be outdated and no longer applicable.

×
  • Create New...