Jump to content

Explanation of postinit functions?


Recommended Posts

I'm really trying hard to understand lua. I think I'm halfway smart at least, and I even have some experience with coding(very little in recent years, but I understand the foundations pretty well.. or thought I did) but this stuff is just confounding me. Anyway, I've tried searching in various ways to find where the AddSimPostInit and other "post init" functions are defined, and I'm having no luck. I've read through tons of forum posts, spent hours on lua tutorials, etc.

 

I was thinking this may be a pre-defined lua function and not specific to Don't Starve, but it doesn't seem to be. Can someone explain the best they can in plain english what a "post init" is/does exactly? And AddSimPostInit and PrefabPostInit and all the various PostInit functions?

 

Every post I can find in the forums talks about "post init" functions like they are a thing that everyone already knows and understands. Can someone really dumb it down for me? But with some good details too..

Link to comment
Share on other sites

PostInit functions are added to each mod environment, which is what modmain.lua files are run under (note: this means that the mod API functions don't exist in prefab/component/etc files; only in modmain.lua and scripts modimported from within modmain.lua). You can see where the PostInit functions are defined and what they do in the file modutil.lua, function InsertPostInitFunctions. You can see where the mod environment is created and what all is included in mods.lua's function CreateEnvironment (line 70).

Basically, though, when you call an AddPostInit function, you give it a callback function. The game adds that callback function to a table of functions that it will call whenever the specified event happens. So, for example, when you do

 

AddPrefabPostInit("prefabname", function(inst)     print("prefab "..tostring(inst.prefab).." postinit called") end)
The game will add your function to the list of functions to run whenever a new "prefabname" prefab is created.

For usage examples/explanations of what specific mod API functions do, check out the API Examples mod.

One major thing about Lua that I found very helpful once I realized: there are very few real 'types' and most everything is tables. The 'mod environment' I mentioned above is basically just a table of functions and variables that you have access to (the keys are the variable names, the values are the variable values). The global environment is the same thing; just a table like any other filled with every variable and function that is defined (though it has a special key '_G' that references itself (_G._G = _G)). When you define a global variable or function, you are simply adding a value to the table with the key of the variable name (function GlobalFunc() end is the same as _G["GlobalFunc"] = function() end). Note: The global environment is stored in the variable GLOBAL within the mod environment (so to access something from the global environment while in the mod environment, you retrieve it from the GLOBAL table like so: local require = GLOBAL.require).

Another major thing: Tables and functions are always handled by reference, meaning:

local table = {}local a = tablea["test"] = "value"print(table.test) -- table.test is the same as table["test"]
will print "value"

Another major thing: Lua by default does not have any object-orientated programming capabilities. 'Class' is actually a function that returns a table (and is not part of the standard Lua library). Don't Starve uses a (slightly modified; see class.lua) version of this implementation: Simple Lua Classes

Link to comment
Share on other sites

Thx for explanation about classes. But what about ".lua" files and local variables/function visibility?

Its works like single LUA files is class. With private and public methods/functions.

 

Only things is missed here - call for parent(or parent of parent) function without saving old one in variable and things like class constructor/destructor. Well some kind of constructor is here. While destructor is not really required, you dont need manually destroy buffers/classed variables/whatever.

Link to comment
Share on other sites

Ok, so.. hmm. I think I understood about a third of that, but it did help quite a bit.

 

My first, and most, programming experience comes from original BASIC when I was a kid, which I played with quite a bit. So variables, loops, basic program structure, etc., I feel pretty solid in my foundations. I learned a bit of C++ and later(about a year ago) studied Java a decent amount, so I think I also have a good foundation of object-oriented programming. But I've never taken it to a level of practical use much at all, just study and tinkering, so I've still got a ton to learn.

 

But anyway(I tend to ramble, sorry).. the explanation about Lua not being truly object oriented is a BIG helper. That's been confusing me a ton. When I see env.postinitfns.SimPostInit, I assume env is a main class, and postinitfns is a subclass of that, and so on.. So to reword what you said, am I understanding this right? Basically Lua emulates objects and classes by putting functions as elements within tables inside tables inside tables, etc? So when I see env.postinitfns.SimPostInit, "env" is a table, "postinitfns" is a table stored inside "env", and "SimPostInit" is a function stored as one element within the table "postinitfns"? Oops, actually it looks like SimPostInit is also a table, but then AddSimPostInit is a function stored in that table....?

 

I just got the API example mods a couple days ago and have been perusing it. It's just such slow going for me.

 

I'm not sure I understood what you meant about the callback function, or what you mean by a "key". I feel like I'm right on the verge, but I think I'll understand better as I code more.

 

I'm curious. I see you post a ton on here and you seem to be very knowledgable. Did you go to school to learn all this or are you self taught? Do you do it for a living or just enjoying making mods and such? Thanks again for the help :)

Link to comment
Share on other sites

Thx for explanation about classes. But what about ".lua" files and local variables/function visibility?

Its works like single LUA files is class. With private and public methods/functions.

Only things is missed here - call for parent(or parent of parent) function without saving old one in variable and things like class constructor/destructor. Well some kind of constructor is here. While destructor is not really required, you dont need manually destroy buffers/classed variables/whatever.

Local variables/functions are somewhat similar to private member variables/functions, but they are members of the file, not necessarily the class (unless you only ever define one class per file, or wrap your class definition in a function).

With Don't Starve's implementation of Class, when using inheritance, the parent class is stored in '_base'. This is mostly used in screens/widgets, like in AnimButton (I'm only posting the relevant code):

 

local AnimButton = Class(Button, function(self, animname)    -- call the Button constructor    Button._ctor(self, "AnimButton")end)function AnimButton:OnGainFocus()    -- this calls Button:OnGainFocus() because AnimButton._base == Button    AnimButton._base.OnGainFocus(self)end
For destructors, they exist in the cases that you might need them. For components, you can define ComponentName:OnRemoveEntity() which will get called when the prefab it was a part of is removed from the world and ComponentName:OnRemoveFromEntity() which will get called when the component is removed from the prefab it was a part of. The same can be done with prefabs (inst.OnRemoveEntity = function() end)
Link to comment
Share on other sites

oh its pretty cool example, thx.

 

one more question - is it possible to know what class is used in current variable? like :

a = Buttonb = AnimButton function testclass(cc)if cc == Button then endif cc == AnimButton then endendtestclass(a)testclass(b)
Link to comment
Share on other sites

But anyway(I tend to ramble, sorry).. the explanation about Lua not being truly object oriented is a BIG helper. That's been confusing me a ton. When I see env.postinitfns.SimPostInit, I assume env is a main class, and postinitfns is a subclass of that, and so on.. So to reword what you said, am I understanding this right? Basically Lua emulates objects and classes by putting functions as elements within tables inside tables inside tables, etc? So when I see env.postinitfns.SimPostInit, "env" is a table, "postinitfns" is a table stored inside "env", and "SimPostInit" is a function stored as one element within the table "postinitfns"? Oops, actually it looks like SimPostInit is also a table, but then AddSimPostInit is a function stored in that table....?

Yes, env is a table defined in mods.lua's CreateEnvironment function like so:

	local env = 	{		TUNING=TUNING,		modname = modname,		pairs = pairs,		ipairs = ipairs,		print = print,		math = math,		table = table,		type = type,		string = string,		tostring = tostring,		Class = Class,		GLOBAL = _G,		MODROOT = "../mods/"..modname.."/",		Prefab = Prefab,		Asset = Asset,		Ingredient = Ingredient,	}		-- other stuff that I removed for clarity	modutil.InsertPostInitFunctions(env)
env.postinitfns is accessing the key 'postinitfns' of the table 'env'. env.postinitfns.SimPostInit is accessing the key 'SimPostInit' of the table env.postinitfns. AddSimPostInit is not added to the env.postinitfns.SimPostInit table, it is added to the env table (env.AddSimPostInit = function(fn) etc).

 

I'm not sure I understood what you meant about the callback function, or what you mean by a "key". I feel like I'm right on the verge, but I think I'll understand better as I code more.

Callback function is a term that signifies any function given to some system in order for that system to call the function whenever some event happens. It's like handing it off for something else to keep track of when to call it and you can just worry about what to do when that thing happens. So, every function passed into an AddPostInit function can be considered a callback function.

I said key instead of the standard programming term 'index' because Lua tables are insanely flexible with table indexes. They can literally be anything. For instance, you can define a table like so:

local tbl = {}local nofn = function() return "no" endlocal yesfn = function() return "yes" endtbl[nofn] = "nope"tbl[yesfn] = "yep"
So, in this case, the functions are the keys to the table. To access the value "nope", you'd have to index into the table using the 'nofn' variable. You could also iterate over the table like so:

-- must use pairs here; ipairs is only for array-like tables (sequential integer keys starting at 1)for fn,value in pairs(tbl) do    -- I can call the keys!    print( fn() )end
Also a note on table syntax:

local tbl = {}-- using the var.key syntax below is simply a shortcut to access a key that is a stringtbl.key = nil-- the above is the same as:tbl["key"] = nil

I'm curious. I see you post a ton on here and you seem to be very knowledgable. Did you go to school to learn all this or are you self taught? Do you do it for a living or just enjoying making mods and such? Thanks again for the help :-)

Fully self-taught in Lua. Took some C++ programming classes and such. I was introduced to Lua doing level scripting for a different game (this one), but once I started modding Don't Starve I realized I had barely scratched the surface of what's possible with Lua. The Don't Starve codebase is really, really well done and it's an excellent learning tool.

EDIT: Changed 'table' variable names to 'tbl' because 'table' is a standard library variable and it's probably not a good idea to overwrite it :-)

Link to comment
Share on other sites

oh its pretty cool example, thx.

 

one more question - is it possible to know what class is used in current variable? like :

Yes, the Class implementation adds the function 'is_a' to all objects, so you can do something like this:

function IsObject(obj)    return type(obj) == "table" and obj.is_aendfunction IsInstanceOfClass(obj, class)    return IsObject(obj) and obj:is_a(class)endlocal btn = Button()print( "Is Button: "..tostring(IsInstanceOfClass(btn, Button)) )print( "Is AnimButton: "..tostring(IsInstanceOfClass(btn, AnimButton)) )
Link to comment
Share on other sites

Yes, env is a table defined in mods.lua's CreateEnvironment function like so:

	local env = 	{		TUNING=TUNING,		modname = modname,		pairs = pairs,		ipairs = ipairs,		print = print,		math = math,		table = table,		type = type,		string = string,		tostring = tostring,		Class = Class,		GLOBAL = _G,		MODROOT = "../mods/"..modname.."/",		Prefab = Prefab,		Asset = Asset,		Ingredient = Ingredient,	}		-- other stuff that I removed for clarity	modutil.InsertPostInitFunctions(env)
env.postinitfns is accessing the key 'postinitfns' of the table 'env'. env.postinitfns.SimPostInit is accessing the key 'SimPostInit' of the table env.postinitfns. AddSimPostInit is not added to the env.postinitfns.SimPostInit table, it is added to the env table (env.AddSimPostInit = function(fn) etc).

 

Callback function is a term that signifies any function given to some system in order for that system to call the function whenever some event happens. It's like handing it off for something else to keep track of when to call it and you can just worry about what to do when that thing happens. So, every function passed into an AddPostInit function can be considered a callback function.

I said key instead of the standard programming term 'index' because Lua tables are insanely flexible with table indexes. They can literally be anything. For instance, you can define a table like so:

local table = {}local nofn = function() return "no" endlocal yesfn = function() return "yes" endtable[nofn] = "nope"table[yesfn] = "yep"
So, in this case, the functions are the keys to the table. To access the value "nope", you'd have to index into the table using the 'nofn' variable. You could also iterate over the table like so:

-- must use pairs here; ipairs is only for array-like tables (sequential integer keys starting at 1)for fn,value in pairs(table) do    -- I can call the keys!    print( fn() )end
Also a note on table syntax:

-- using the table.key syntax below is simply a shortcut to access a key that is a stringtable.key = nil-- the above is the same as:table["key"] = nil
Fully self-taught in Lua. Took some C++ programming classes and such. I was introduced to Lua doing level scripting for a different game (this one), but once I started modding Don't Starve I realized I had barely scratched the surface of what's possible with Lua. The Don't Starve codebase is really, really well done and it's an excellent learning tool.

 

 

I think I'll have to look at this again with a fresh brain when I wake up(I'm in the US. I just have odd hours and this is "late" for me.) My brain just isn't catching onto any new concepts for the night. I can follow patterns pretty well, just in general, and from my little bit of previous coding experience, but I guess honestly I never fully caught on to object oriented languages. Or ones that act like them. Branches and loops, even complex ones, I can usually figure out, but it's when you have something that calls something else that points back to first thing and branches off to two other things, goes back to second thing and then refers to itself within itself, and so on and so forth, <--- just an example of how it feels to me. But that's when my brain just starts going haywire. I'll definitely be coming back to this post though.

 

Anyway, very cool game you linked. Congrats on the greenlight! That's something I may actually play. I seem to see a bit of Tribes influence in there, which is my favorite current shooter.

Link to comment
Share on other sites

env.postinitfns is accessing the key 'postinitfns' of the table 'env'. env.postinitfns.SimPostInit is accessing the key 'SimPostInit' of the table env.postinitfns. AddSimPostInit is not added to the env.postinitfns.SimPostInit table, it is added to the env table (env.AddSimPostInit = function(fn) etc).

 

Ok, after looking at modutil.lua and mods.lua some more today and reading your posts again, I think maybe I'm understanding the term "keys" better. Maybe. I wanna see if I'm right. I think most of this is what you said already, but it helps me to put in my own words and see if I've got it right.

 

So in this section of modutil. lua -

 

188    env.postinitfns.SimPostInit = {}

189    env.AddSimPostInit = function(fn)

190        initprint("AddSimPostInit")

191        table.insert(env.postinitfns.SimPostInit, fn)

192    end

 

Line 188 creates a new table called SimPostInit inside the table postinitfns, and SimPostInit is also the "key" to access that table(which is an element of the table postinitfns) inside postinitfns later on, correct?

 

Line 189 defines a new function called AddSimPostInit. This function is an element(is "element" the correct term?) of the table env. What the function does is calls the function initprint with the parameter "AddSimPostInit". Next, it(line 191)... hmm, this I think confuses me.. but it then inserts a new element at the end of the SimPostInit table. The thing it inserts is the... returned value from the AddSimPostInit function?

Link to comment
Share on other sites

Very close to correct. Line 191 inserts the fn parameter that the AddSimPostInit function was called with to the end of the env.postinitfns.SimPostInit table (table.insert(tbl, value) is equivalent to tbl[# tbl] = value, and # tbl is a shortcut to get the length of an array-like table [note: table.insert and # tbl should only be used on array-like tables, if you have non-sequential keys, or any non-integer keys, it won't function as expected. To get the length of any arbitrary table, you can use the helper function GetTableSize defined in util.lua]).

So, if you do something like this in modmain.lua:

local testfn = function() endAddSimPostInit(testfn)
That calls the env.AddSimPostInit function and passes it the parameter testfn, which means that the testfn function (more accurately, a reference to the function that the testfn variable holds) will get inserted into the env.postinitfns.SimPostInit table on line 191.

Oh, and, yes, element is the correct term.

Link to comment
Share on other sites

Very close to correct. Line 191 inserts the fn parameter that the AddSimPostInit function was called with to the end of the env.postinitfns.SimPostInit table (table.insert(tbl, value) is equivalent to tbl[# tbl] = value, and # tbl is a shortcut to get the length of an array-like table [note: table.insert and # tbl should only be used on array-like tables, if you have non-sequential keys, or any non-integer keys, it won't function as expected. To get the length of any arbitrary table, you can use the helper function GetTableSize defined in util.lua]).

So, if you do something like this in modmain.lua:

local testfn = function() endAddSimPostInit(testfn)
That calls the env.AddSimPostInit function and passes it the parameter testfn, which means that the testfn function (more accurately, a reference to the function that the testfn variable holds) will get inserted into the env.postinitfns.SimPostInit table on line 191.

Oh, and, yes, element is the correct term.

 

 

Ok, I think I'm catching on to this a bit now. You confused me with this though -

 

table.insert(tbl, value) is equivalent to tbl[# tbl] = value

 

Wouldn't this replace the last value in the table rather than adding an additional value? Let's say we have...

 

tbl = {6, 5, 43, 720, 12}

 

So if I then use

 

table.insert(tbl, 453)

 

then now

 

tbl = {6, 5, 43, 720, 12, 453}

 

but if I use

 

tbl[# tbl] = 453

 

then

 

tbl = {6, 5, 43, 720, 453}

 

and you've replaced the 5th element rather than adding a 6th element because # tbl = 5 to begin with. Or am I missing something?

Link to comment
Share on other sites

tables in programming languages start at position '0' not  '1'.   so for a table with N elements..   myTable[N]  is actually not the last element but one position AFTER the last element.  Depending on the language this will either create a new element automatically or crash the program (C languages go the crash route)

Link to comment
Share on other sites

Wouldn't this replace the last value in the table rather than adding an additional value?

Whoops, you're totally correct. I meant that table.insert(tbl, value) is equivalent to tbl[(# tbl)+1] = value

Also worth noting (but it seems like you already know) that array-like tables in Lua have a starting index of 1 (as opposed to 0 like many other programming languages)

local tbl = {}table.insert(tbl, "something")print( tostring(tbl[0]) ) -- prints "nil"print( tostring(tbl[1]) ) -- prints "something"
Link to comment
Share on other sites

Also worth noting (but it seems like you already know) that array-like tables in Lua have a starting index of 1 (as opposed to 0 like many other programming languages)

 

 

I actually only knew that because it seemed intuitive. I had no idea other languages start at 0. So kind of a lucky guess. Ok, well I'm feeling kind of proud that I actually figured something out. Thanks for the extensive help.

Link to comment
Share on other sites

heh,  well I hadnt yet noticed that lua has a starting index of 1 instead of 0.  Good to know

 

I was about to ask you why this worked the way I described when I put it in SciTE, but as I said above, I guess I just got a lucky guess because lua is the first language I've actually done anything besides read about.

Link to comment
Share on other sites

Small question is it safe to use AddPrefabPostInit if prefab not exists?

 

I am looked into code and saw that this functions saved in

    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

and assigned to prefab here

function RegisterPrefabs(...)    for i, prefab in ipairs({...}) do        --print ("Register " .. tostring(prefab))        -- allow mod-relative asset paths        for i,asset in ipairs(prefab.assets) do            local resolvedpath = resolvefilepath(asset.file)            assert(resolvedpath, "Could not find "..asset.file.." required by "..prefab.name)            TheSim:OnAssetPathResolve(asset.file, resolvedpath)                        asset.file = resolvedpath        end        prefab.modfns = ModManager:GetPostInitFns("PrefabPostInit", prefab.name)        Prefabs[prefab.name] = prefab                TheSim:RegisterPrefab(prefab.name, prefab.assets, prefab.deps)    endend

so if prefab doesnt exists this function just will be never called?

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...