[LUA] OO possible with CivBE lua implementation?

Wardly

Chieftain
Joined
Nov 9, 2014
Messages
39
Aside from the mod i've written (http://steamcommunity.com/sharedfiles/filedetails/?id=342911879) I have zero experience with LUA. However i'm not new to programming and find it hard to program in procedural fashion.

I've been trying to come up with a way to get object oriented programming with the current implementation of CivBEs lua. It seems some items have been left out most likely for security reasons and what not.

It seems that the class declaration is something that wasn't included. Not entirely sure why that is. I can't include the class file since the include method has been rewritten to only include items in the VFS and not include the actual LUA files. I'm not stuck on using that particular file. But has anybody been able to come up with a working object oriented approach to the lua with the game?

Now i know i can declare functions inside functions to get something like parent.child() but that isn't the same as many of the characteristics are different. For example i have CRUD (create, read, update, delete) actions that i use to interact with the database that can be called in any number of functions. I'd like to create an object for them and then extend that object with my table classes. Creating a makeshift orm if you will. Using the PRAGMA table_info(table_name) i can even get the table schema to initialize empty table objects for use on inserts and what not. Doing these sort of things with a function inside of a function just isn't practical and i might as well just leave it as is, a collection of one off functions invoked independently.

Does anybody have any examples of something remotely close to OOP? i know LUA can achieve oop but it seems like every attempt to do this in game literally crashes civ and i have to ctr+alt+del the game.
 
Yes, you can actually utilize Lua's table structure to implement classes and such. Take a look at the source code for my mapscript if you'd like to see some examples (it's a pretty large script so I'll see if I can get you some key points to search for). With some trickery cephalo found out there on the interwebz it's even possible to mimic subclasses as well.

Search "FloatMap Class" to see the first definition of a class and it's members as well. The member definition looks like this:
Code:
function FloatMap:New(args)
but it's actually doing this:
Code:
FloatMap.New = function(self, args)
where self is the FloatMap you're invoking "New" on.

From there, the script goes on to create quite a number of specific subclass FloatMaps (ElevationMap is the first and one of the simplest, RiverMap is one of the more complex ones and includes a member class which also has a member class!).

Also, keep in mind that Lua's tables are completly object oriented and that reassignment does not create a new table:
Code:
a = {}
b = a
b[1] = 9000
print a[1]
>9000
 
Planet Simulator

Edit: The trickery being used to create subclasses is the inheritsfrom() function. I've taken a look at it many a time and I still barely understand it's ~20 lines... :mischief:
 
Another example here

Take care if using events with multiple instances, you'll need to catch the event in a (static) class method (or within a factory) and pass it on to the individual instances rather than catching it in each instance
 
Thats an interesting approach to creating inheritance. If i'm reading it correctly, its basically just creating a metatable with all the methods in it and then when you "extend" it by using the inheritsFrom function you overwrite the __index element of the metatable with the new class name but it keeps all the methods in the metatable of the original class. Hence the first two lines in each of the New functions.

it appears though that there is some built in scope as you can reach the 'parent' class by using the baseClass (which is just a wrapper) or the _B function to invoke parent functions or vars independently of the child by class name.

So basically the first 'class' starts the inheritance with an empty metatable. But as you build new functions in the class each is added (default lua functionality unrelated to the code here) to the objects metatable.

but when the inheritsFrom isn't nil it gets the metatable of whatever you pass into it. Thereby giving the full set of functions and vars to the child that the parent (metatable passed in) originally had. You should also be able to reach each of the parents (assuming you have more then one) by there name and then tap into any functions and variables that the metatable contains. Although it shouldn't be needed
since you can use self.<method or var> due to the metatable

so correct me if i'm wrong but i would be able to do something like the following (due to the new_class:class() function),

Code:
parent = inheritsFrom(nil)

function parent:New()
    local new_inst = {}
    setmetatable(new_inst, {__index = parent})

    myVar = "something"
end


child = parent:class()
print(child.myVar) -- should print out "something"

-- or something like 

child = parent:class()
child.myVar = "something else"
print(child.myVar) -- should print "something else"

Granted that the new child wouldn't be able to be extended further without using the inheritsFrom function.

Is all that correct and what your seeing?
 
Yes, you've pretty much covered the bases.

All of the maps Planet Simulator uses are subclasses of FloatMap. Any given map therefore has access to all of FloatMap's members. FloatMap:Normalize() is a good example as almost every subclass map utilizes it.

Note the following code snippets are from my WIP v3 where I've been localizing everything I possibly can as it's apparently bad to use Globals if they're unnecessary and it speeds up access times as that's one less table lookup in the chain to get to any given datapoint indexed in one of my floatmaps.

Initializing ElevationMap like so:
Code:
local ElevationMap = inheritsFrom(FloatMap)
allows access to all of FloatMap's members:
Code:
ElevationMap:Normalize()

Also, on a slightly related note to the above, apparently the Havok Script parser either renamed _G or has changed how globals are stored... I'm not entirely sure on which yet, as Havok Script has no readily available resources for the public (only propaganda and marketing).
 
Another example here

Take care if using events with multiple instances, you'll need to catch the event in a (static) class method (or within a factory) and pass it on to the individual instances rather than catching it in each instance

Can you elaborate? i would imagine if i init a class on turn one and init another on turn two using the exact same code it would overwrite the original from turn 1 rather then creating a clone leaving me with two. i'm guessing theres more to your statement then that though.
 
I'd like to pop back in here and say a few things.

This discussion led me on a goose chase for resources regarding OO in lua which ended up with me reading guides on how to optimize Lua. Long story short, everything is bad and kills performance. I've been privatizing and de-classing and optimizing and whatnot for two days now and it's actually sped my script up dramatically. I can't get anything better than 1-second accuracy due to Havok Script :cringe: but I've gotten Large maps down from 6-7 seconds to 2-3 seconds and I'm not even done destroying all the classes... Granted, I feel most of the optimization I'm going to see out of this has already come due to the fact that I have privatized or de-classed most of the functions getting called thousands of times during my loops that scan the map(s). I actually got about a second of that optimization by localizing the math library functions:
Spoiler :
Code:
local math_sin = math.sin
local math_cos = math.cos
local math_pi = math.pi
local math_abs = math.abs
local math_floor = math.floor
local math_ceil = math.ceil
local math_sqrt = math.sqrt
local math_pow = math.pow
local math_log = math.log
local math_exp = math.exp
local math_random = math.random
local math_randomseed = math.randomseed
local math_min = math.min
local math_max = math.max

Because lua requires you to use it's table structure to achieve OO functionality, and because of the way those table structures work (particularly the part about indexes "not == {1, n} over the integers" being stored as part of the table's hash and requiring an expensive hash lookup) it really isn't the best approach if performance is a concern. The deeper into a chain of tables you get, the more expensive the lookup becomes as well (which also applies to that inheritance function; the deeper into a class structure you go, the further back it has to go to find the base's members).

If you aren't performing operations thousands upon thousands of times then OO probably isn't going to have much of an impact; however, if you're iterating over large tables (5000+ indexes) and performing operations buried under a stack of table lookups hundreds of times throughout the course of your script (like I am...) OO probably isn't worth the effort unless that's the only way you feel comfortable programming. I personally will be keeping "pseudo-classes" for the things that it makes sense for (where the "psuedo-member" is actually an upvalue available to everything in the script but it shares a name with it's "psuedo-class" i.e.: FloatMap_Normalize()), but that's about it.
 
Interesting. It would make sense i suppose when looping through methods several thousand times. I'll have to see if there's any drawback on my usage, i intended to just converting my database file and run a few patches with that before converting other parts of the mod. Also my scripts only run when a unit is selected, created, or just before battle. Each process has no more then a few queries, which would roughly result in 2 - 8 method calls. So i'm nowhere near the number your dealing with. I'm going to assume the performance hit on what i'm currently doing would be negligible.

I appreciate the feedback as i have some plans that would require looping calls over a number of selected units in order to calculate multiple unit movements at once. I'm sure i still wont hit the number you have but it might still play a role at that point.
 
Top Bottom