• No results found

Evaluating Lua for Usein Computer Game Event Handling

N/A
N/A
Protected

Academic year: 2022

Share "Evaluating Lua for Usein Computer Game Event Handling"

Copied!
40
0
0

Loading.... (view fulltext now)

Full text

(1)

Evaluating Lua for Use in Computer Game Event Handling

O S K A R F O R S S L U N D

Master of Science Thesis

(2)

Evaluating Lua for Use in Computer Game Event Handling

O S K A R F O R S S L U N D

DD221X, Master’s Thesis in Computer Science (30 ECTS credits) Degree Progr. in Computer Science and Engineering 300 credits Master Programme in Computer Science 120 credits Royal Institute of Technology year 2013 Supervisor at CSC was Douglas Wikström

Examiner was Jens Lagergren TRITA-CSC-E 2013:019 ISRN-KTH/CSC/E--13/019--SE ISSN-1653-5715

Royal Institute of Technology School of Computer Science and Communication KTH CSC SE-100 44 Stockholm, Sweden URL: www.kth.se/csc

(3)

Abstract

For this thesis I have studied the difference between using parsed scripts and embedded scripts (in this case Lua) in the context of evaluating computer game in-game events.

The major focus of this thesis is the difference in perfor- mance between the two approaches and a minor focus has been the effect they have on the development process.

The context of this thesis has been limited to the com- puter game Europa Universalis III (EU3) by Paradox De- velopment Studio (Paradox) and the study has been per- formed by constructing a framework for evaluating in-game events using Lua in said game. Runtimes of the Lua version of events were then compared to runtimes of the original events.

Lua is an embedded scripting language that has been used in computer games since the 1990s and is known to have a small memory footprint and to be fast. The original evaluation system in use in EU3 is based on parsed script files that are executed as C++ code. What are the benefits and drawbacks of the different approaches?

Using an embedded scripting language like Lua clearly has some advantages compared to the more static method of parsing script files, mainly in the way of flexibility and the simplicity of adding new features. Parsed scripts, on

(4)
(5)

Referat

Utvärdering av Lua för användning vid hantering av händelser i datorspel

För detta arbete har jag undersökt skillnaderna mellan att använda parsade skriptfiler och att använda ett inbäddat skriptspråk (embedded scripting language, i det här fallet skripspråket Lua) för att utvärdera spelhändelser i dator- spel. Tyngdpunkten för undersökningen är skillnaden i pre- standa mellan de båda tillvägagångssätten men även effek- ten de har på utvecklingsarbetet har studerats.

Arbetet har avgränsats till att bara innefatta datorspe- let Europa Universalis III (EU3) av Paradox Development Studio (Paradox) och undersökningen har genomförts ge- nom att konstruera ett ramverk för att evaluera spelhän- delser med hjälp av Lua i detta spel. Körtider för de Lua- baserade spelhändelserna har sedan jämförts med körtider för originalen.

Lua är ett inbäddat skriptspråk som har använts i da- torspel sedan 1990-talet och är känt för att ta upp små mängder minne och för att vara snabbt. Det ursprungliga händelsehanteringssystemet som används i EU3 bygger på skriptfiler som parsas och körs som C++-kod. Vad finns det för för- och nackdelar med de två tillvägagångssätten?

Att använda ett inbäddat skriptspråk som Lua har helt klart sina fördelar jämfört med den mer statiska metoden att använda parsade skriptfiler, framförallt i fråga om flexi- bilitet och lättheten med vilken man kan lägga till ny funk-

(6)
(7)

Preface

Target Audience

The target audience of this thesis would be developers considering integrating Lua into a C/C++ product but could also be of interest on a general level for developers considering embedding any scripting language into their product. It would also be of interest to those who are interested in Lua performance in general although the setting here is rather specific.

Acknowledgements

I would like to thank Jimmy Rönn for helping me to understand the labyrinth of EU3’s event system and being an excellent sounding board to bounce my ideas off of. I would also like to thank Mike Pall and everyone else on the Lua mailing list[4]

for helping me out and putting up with all my questions.

(8)
(9)

Contents

1 Background 1

1.1 Problem . . . 1

1.1.1 In-game Events . . . 1

1.1.2 Embedded Scripts vs. Parsed . . . 3

1.2 Lua . . . 4

1.2.1 General Lua . . . 4

1.2.2 Lua and C . . . 5

1.2.3 LuaJIT . . . 6

1.2.4 Historical Use of Lua in Computer Games . . . 6

2 Method 7 2.1 Workflow . . . 7

2.2 Testing . . . 7

2.2.1 Purpose of Testing . . . 7

2.2.2 Method of Testing . . . 8

3 Results 9 3.1 Outline of the Lua Setup . . . 9

3.2 Improvements . . . 10

3.2.1 Strings . . . 10

3.2.2 Minimising Transitions . . . 10

3.2.3 Databases . . . 11

3.3 Lua vs. LuaJIT . . . 12

3.4 Results . . . 13

3.4.1 Performance . . . 13

3.5 Conclusions . . . 16

3.6 Future Work . . . 16

4 Summary 19

Bibliography 21

(10)

A Comparison of New and Original Event Files 23 A.1 A New Event . . . 23 A.2 An Original Event . . . 24

B Test Code 25

(11)

Chapter 1

Background

1.1 Problem

1.1.1 In-game Events

An Overview of the Structure of Computer Games

Most people have an idea of what a computer game is and most of the ideas are probably correct even though they might differ greatly.

I would characterise a computer game as an interactive computer program which offers the user some sort of challenge and most often has a defined goal. This, of course, leave plenty of room for variations.

Computer games range from simple text based programs, prompting the player for answers to simple questions, to massive games featuring stunning graphics and audio where players can immerse themselves in challenging adventures together with people from all over the world. As different as the games might be, they still share some basic structural features.

Figure 1.1 describes my view of a basic structure for computer games. Starting from the right the player uses the user interface (UI) to communicate with the game engine. This interface might contain only text (like a command prompt) or use graphics (graphical UI or GUI). It may also include audio. The game engine then processes the user input and manipulates the game state according to the input and the game rules.

The game state, as the name implies, contains all the information on how the situation in the game looks at this exact moment. This can be as simple as keeping track of what questions the player has answered correctly to containing, as is the case in EU3, all the economical, political, industrial and military information of all countries in the game world. Typically, when you save an instance of a game, you save the current game state.

Most games also contain some form of AI (artificial intelligence). Even though it is mostly only the non player opponents in a game that are actually called AI, everything the player is not in control of has to be governed by some form of it. The

(12)

CHAPTER 1. BACKGROUND

Figure 1.1: Typical structure of a modern computer game

AI typically works by first examining the game state and then matching conditions to a given set of rules on how it should behave. If the condition for an action is met, that action will be taken. If there is a possibility that several actions can be viable at the same time the rules must also include some way to choose between those actions. This can be done, for instance, by assigning priorities to actions and having the AI always choose the viable action with the highest priority.

Lastly, any relevant changes to the game state will be advertised to the player via the UI.

In-game Events

In-game events are what the name implies; events happening in the game. The events are triggered by the game AI and changes the game state in some way.

In EU3 the in-game events represent those events in the game that appear to be random.

I have focused this study on a part of the in-game events of EU3 called the trigger. There are other parts of an event but for this thesis the trigger is the most interesting one as it is what requires the most computing resources in the event evaluation process. The other parts, although necessary for the complete in-game event process, do not have the same central role in the event evaluation as the trigger.

The trigger is a logical expression evaluated against the game state and consti- tutes the prerequisite for an event to occur. When the in-game events are parsed from script files1 the trigger is built as a tree like logical expression structure where

1For an example of such a file, see appendix A.2.

2

(13)

1.1. PROBLEM

each leaf is a lookup on the game state and each inner node is a logical operator.

The evaluation of the trigger can then be performed by recursively traversing the tree structure from the root node, collecting the logical result.

The whole evaluation process is performed in C++, and C++ is fast. As such, the whole evaluation process will also be fast. This is a necessity as there is a lot of event evaluation performed throughout a game session and this can not be allowed to overly impact system performance as it would result in less enjoyable gameplay. The fact that the whole EU3 event handling system is written in C++

does, however, also come with a price which I will explore further in section 1.1.2.

For the performance focus of this study I have compared the runtimes of trigger evaluations using Lua to the runtimes of the same trigger evaluations using the existing event evaluation system of EU3.

1.1.2 Embedded Scripts vs. Parsed

The difference between embedding a scripting language into your program and pars- ing script files to specify the behaviour of it, can generally be summarised as usability versus speed.

Using an embedded language you will have the benefit of having a fully func- tional programming language at your disposal, requiring far less complementary code to work compared to writing your own parser. You still have to define much of the final functionality either way but an embedded scripting language might reduce your work load thanks to its completeness. For instance, introducing some- thing as simple as different kinds of integer comparisons while using parsed scripts will leave you, either with implementing several different comparator functions, or adding more complexity to your parser to handle those comparisons. An embedded scripting language will come with much of the basic functionality out of the box, only requiring you to implement more advanced functionality (that you still have to implement if using parsed scripts).

The downside to using embedded scripting languages lies in performance. To actually use the scripted functionality you will have to leave the main program and hand execution over to the scripts. This is bad for two reasons:

• Switching back and forth between executing the main program and executing scripts adds runtime overhead which is not present when using parsed scripts.

Any sending of information between the two parts will also add overhead compared to handling information solely within the main program.

• Scripting languages are often slower than the language you are using for your main program.

The actual parsing needed when using parsed scripts will, on the other hand, also generate execution overhead. This overhead could be significant if you are consid- ering using an embedded script that runs nearly as fast as your main program and

(14)

CHAPTER 1. BACKGROUND has a fast compiler. Of course the parsing cost is usually a one time cost but it will be necessary to consider if the whole run time of your program is of importance and not just the actual execution after start-up.

For the usability focus of this study I will highlight the difference between using an embedded scripting language and parsed scripts by discussing how using Lua could affect the development of games like EU3.

1.2 Lua

In this section I will give a brief introduction to the scripting language Lua. The language of choice for many computer game manufacturers and a language that is both fast, powerful and limited in size. This introduction will hopefully give you a basic understanding of the language and help you better understand the rest of this paper. If you want to know more there are many excellent sources of information, for example the official Lua homepage[2].

1.2.1 General Lua

Lua was created in the early 1990s by people of the Pontifical Catholic University of Rio de Janeiro, Brazil, and one of the main ideas was that it should be embeddable in C. All source code for Lua is written in ANSII standard C and is about 17000 lines of code. It is dynamically typed, it has built-in garbage collection and it uses functions as first-class values. Everything in Lua is stored in tables and in tables within tables. The global namespace is accessible as a table and any kind of variable assignment is analogous with setting a field in that table.

The data types used by Lua are boolean, number, string, userdata, function, thread, table and nil. Non assigned variables are treated as nil and indexation normally starts at 1. Lua is not originally designed to support multi-threading and consequently the threads in Lua are not run simultaneously.

One feature of Lua that has been very useful in my work is Lua’s built-in string manipulation and regular expression facilities.

An Example of Lua Code

Below is a code example to illustrate the syntax used in Lua. The code displayed is a small program I wrote and used to count the number of times different functions were used in the EU3 event files. The program will, when run, read the file events/filelist line by line and count occurrences of function names in the file specified on the line.

funcs = {}

function list_funcs(str)

for match in str:gmatch("%a[%a_]*%b()%s+>?=%s+%d+") do 4

(15)

1.2. LUA

if not funcs[match] then funcs[match] = 1 else

funcs[match] = 1 + funcs[match]

end end

for match in str:gmatch("(%a[%a_]*%b())%s+[^>=]") do if not funcs[match] then

funcs[match] = 1 else

funcs[match] = 1 + funcs[match]

end end end

for line in io.lines("events/filelist") do if line then

local file = io.open("events/" .. line, "r") local content = file:read("*a")

for match in content:gmatch("trigger%s+=%s+%b[]") do list_funcs(match)

end

for match in content:gmatch("trigger_prefix%s+=%s+%b[]") do list_funcs(match)

end

file:close() end

file = io.open("funclist_unsort", "w") for k, v in pairs(funcs) do

file:write(k .. "\t" .. v .. "\n") end

file:close() end

1.2.2 Lua and C

As mentioned in the previous section, Lua is designed to be embedded in C. This is facilitated by the use of a special lua_State object and an extensive C application programming interface (API). The C API provides functions for manipulating the lua_State and loading and running Lua code in it. The state holds information from loaded Lua files and also the Lua stack which is used for all interaction between Lua and C.

To call a Lua function from C, you first push the function you want to call to the Lua stack, for example using the lua_getglobal method. You then push the

(16)

CHAPTER 1. BACKGROUND arguments in direct order (i.e. first argument is pushed first) and finally you call the lua_call function. This function performs the call and pops the function and all arguments from the stack. The called Lua function should then push all results onto the stack in direct order. The stack itself is usually used in the standard way by pushing and poping values from it but can be manipulated in ways similar to other data structures. It can, for instance, be indexed from both directions. If n is the number of elements on the stack n is also the index of the topmost element (remember, Lua is indexed from 1). The indices 1 to n are called valid indices. The stack can also be indexed with negative numbers where -1 represent the element at the top of the stack. These negative indices are called pseudo indices.

1.2.3 LuaJIT

I used LuaJIT in this project as a way to improve performance. Information on the LuaJIT project can be found at the project homepage[6] and, in short, it is a substi- tute Lua compiler that ”offers more performance, at the expense of portability”[6].

The limitations in portability, however, were not a hindrance to this project as LuaJIT still support most major platforms. The performance increase on the other hand, is rather substantial as I will illustrate in section 3.3.

1.2.4 Historical Use of Lua in Computer Games

Lua is not a newcomer on the computer game development scene. Early on develop- ers realized its potential and now it has been a part of many games that are played by a great many people with World of Warcraft perhaps being the prime example.

Going through the quite extensive list of Lua uses in the Lua users’ wiki[7] one can see that common tasks for Lua are UI handling, in-game event management, game logic management and in-game parameter tweaking. This is very much in line with my work at Paradox, namely in-game event handling.

6

(17)

Chapter 2

Method

2.1 Workflow

To begin this study I implemented a naive version of the EU3 event handling system using Lua and incorporated it into the actual EU3 game. The main purpose of this first version was to ascertain that a setup using Lua could actually be used to handle the event evaluation. The implementation process included adding an alternative event evaluation process to the EU3 game that used Lua, writing a framework of functions to be used by Lua to retrieve the necessary in-game information and translating all the original events to fit the Lua setup.

Having implemented the first version I then iteratively improved my design by locating and reworking parts of the code that had bad performance. Both the evaluation code itself and the event structure were considered.

Section 2.2 describes this evaluation process in more detail and major improve- ments and attempts will be described in section 3.2

2.2 Testing

2.2.1 Purpose of Testing

The testing in this project has been done exclusively on a single PC and was done continuously during the development process. The testing framework was initially used as a tool to locate performance issues of the new event evaluation framework but it was also used to ultimately determine the result of my work.

I deemed using only one computer to be sufficient as I was only interested in a rough estimate of the relative speed of the two setups. I also reasoned that the major performance issues of my code would be more or less architecture independent at least on a scale where significant differences could be observed.

For more exact, in-depth testing a more varied and wider test bed had been preferable but for the results needed to evaluate this project I deemed the presented setup sufficient.

(18)

CHAPTER 2. METHOD

2.2.2 Method of Testing

Testing of the framework was made in-game using a debug and testing console already included in the EU3 source code. To this console I added commands to run single and multiple events while monitor their speed and evaluation for both the Lua setup and the original.

The speed was measured using high resolution timers provided through the Windows environment[5]. I encapsulated the actual event evaluation code, and all code specific to the setup, in a timing environment1 and this allowed me to identify the events that ran slowest using the Lua setup compared to the original.

Once a problematic event had been identified I analysed it using the profiling tool GlowCode[1]. GlowCode allowed me to pinpoint exactly what was the major issues with certain events’ evaluation and thus helped me discover what I might improve.

GlowCode also helped me see how much of the event evaluation was spent on the Lua side and how much was spent in C++.

1For a code sample of the test code, see appendix B

8

(19)

Chapter 3

Results

3.1 Outline of the Lua Setup

Why use Lua?

The reason why Paradox have begun looking into Lua is mainly to see if Lua can be used to improve their development process. The in-game events play a central role in the games developed by Paradox and if the process of implementing them could be improved this would greatly benefit the development team and, in the long run, the entire company.

Using an embedded scripting language to handle events has two distinct advan- tages over using parsed scripts:

• Using an embedded scripting language one can modify the script files in run- time to change and tweak the event behaviour without the need to reload the game (or at least the scripts). This can save a scripter a lot of time when fine-tuning scripts and correcting errors.

• Parsed scripts are just blueprints used by the game engine while embedded scripts are actually run as small programs allowing scripters to add function- ality directly into the script files. A scripter can thus extend the game with new functionality as the need arises instead of having to wait for the new functionality to be added in the game engine. This is especially useful for trying out new ideas.

The problem, however, is that any new event system is not allowed to have any significant impact on system performance.

Event Structure

The actual event files in the new setup are designed as Lua files that, when run at game startup, enter information regarding the events into the lua_State.1 When

1For an actual example of how an event could look using the new syntax, see appendix A.1.

(20)

CHAPTER 3. RESULTS all the event information is in place all triggers will be loaded into the lua_State as functions. Each trigger function return the result of a logical expression where the operands are functions querying the current game state for information.

Event Evaluation

To evaluate the trigger of an in-game event using my setup the event’s trigger function is simply called from C++, supplying the target information (a reference to a country or province).

3.2 Improvements

3.2.1 Strings

One of the major mistakes I made in my first implementation was how I handled strings. I unnecessarily created a lot of strings in my event evaluation and it cost a lot of performance.

The original EU3 code relies heavily on strings used as different flags and iden- tifiers in the databases. Lua is written in C and to pass anything between Lua and C you need to use the Lua stack which, of course, is also then defined in C and can only hold objects defined somehow in C. C++ string object can thus not be put on the Lua stack. This means that if you want a string passed from Lua to C++ you will have to create a new string in C++ at runtime to hold the C representation you get from Lua and this is really performance heavy.

I solved this problem by storing strings on the C++ side and identifying them by integers. I could thus pass integers instead of strings which sped things up significantly. This, however, still requires an extra lookup for each string but it is much better than having to create the strings during evaluation.

This problem is interesting because you easily avoid it by using parsed scripts instead of using an embedded scripting language. When using parsed scripts all strings you use for queries will have to be created and stored somewhere in the parsing process which will save you performance in the actual evaluation. This is the case in EU3. The other way to circumvent this problem is, as I did, to only use data formats common to both the embedded and embedding language.

3.2.2 Minimising Transitions

One significant performance issue I had resulted from the overhead of transitioning between Lua and C++. I tried two different ways to handle this: batching requests together and trying to reduce requests by pre-loading information.

10

(21)

3.2. IMPROVEMENTS

Batch Processing

The idea of batch processing is quite natural and simple: If calling C from Lua is expensive then making one call instead of many must be good for performance. The problem is how to implement this functionality efficiently.

What I did was to make a special C++ function that would be called from Lua with a list of C++ functions as input argument. The functions in the list would then be executed one by one in C++ and the return values would be collected and stored.

When all functions had run the result was returned to Lua. This limited the number of transitions between Lua and C++ but had a mixed impact on performance.

The problem with this approach was that it could not utilize the lazy evaluation inherent in logical expressions that use functions as parameters. If the result of an expression can be determined without evaluating all parameters then the remain- ing parameters can safely be ignored. Collecting all data prior to evaluation thus often meant unnecessary database lookups because some of the fetched values were not needed. In these situations the batch process approach would actually impact performance negatively. There was, however, a performance gain in the cases where all the data was indeed used.

In the end I decided not to use batch processing because, in the majority of cases I examined, only some of the data returned was used. The batch processing approach is still valid but the trick with this kind of optimisation is to identify which operations to batch and which not to and, depending on your program, this can be a very difficult job.

Pre-loading

The concept of pre-loading is related to batch processing. The idea was to identify data that was used repeatedly throughout an evaluation. If this data could be reused throughout the evaluation instead of being fetched anew each time it was needed the number of function calls per evaluation could be limited.

The sheer number of events in EU3 prevented me from implementing this fully but it did improve the performance of the events I tried it on. As with batch processing pre-loading values that are not used at all will decrease performance and selecting values to pre-load should be done with care.

Something that would be more efficient than pre-loading would be some form of evaluation local caching. I did not try this because of it’s complexity and the limited amount of time I had but done correctly it could certainly have improved the performance of my setup.

3.2.3 Databases

Splitting the execution of your program between two different languages leaves you whit the decision of where to put different parts of the execution.

I did some experimentation with moving databases from the C++ side to the Lua side to avoid the overhead of transitioning between Lua and C to collect information.

(22)

CHAPTER 3. RESULTS What I did was to store a snapshot of a database in Lua and compared evaluation times of one version using the C++ database and one version using the snapshot.

Having the database in Lua did improve the performance of the event evaluation but the total impact on the system could not be determined as I did not implement the rest of the database functionality needed by the game. This would have required me to rewrite large parts of the EU3 game so that it stored its information in Lua instead of C++ and I did not have the time needed to do that. The result, however, clarifies the need to properly examine the strengths and weaknesses of the scripting language you want to embed and how to best take advantage of it. Depending on the balance between communication speed between embedded scripts and the main program and the the quality of data handling of the scripting language you need to choose wisely to maximise performance.

3.3 Lua vs. LuaJIT

When evaluating what effect using LuaJIT had on the performance of the program, the program was simply built using the LuaJIT libraries instead of the regular Lua ones and then tested the exact same way as with regular Lua. No changes were made to the Lua event files or the actual source code. Figures 3.1 and 3.2 illustrates the improvement in performance that LuaJIT offered.

Figure 3.1: Difference in average evaluation time of triggers evaluated in C++, Lua and Lua with LuaJIT

In figure 3.1 we can see how the average evaluation time of the triggers differ between the different setups. The average evaluation time was calculated by first timing each event’s evaluation using one Lua setup at a time. The evaluation time was then normalized per event by dividing the Lua evaluation time by a reference

12

(23)

3.4. RESULTS

C++ evaluation time . The average of these normalized evaluation times taken over all events is what is displayed in the figure.

In figure 3.2 you can see the improvement to the evaluation times of events when using LuaJIT instead of regular Lua. The improvement here is calculated as:

1 - (evaluation time using LuaJIT / evaluation time using regular Lua) The average was taken over all events using their respective improvement and as we can see the improvement from using LuaJIT is quite substantial.

Figure 3.2: Improvement in evaluation times of events using LuaJIT instead of the regular Lua compiler

3.4 Results

3.4.1 Performance

Wether or not you want to use Lua in your project all comes down to what you want to use it for. Evaluating logical conditions on data contained in C++ is not where Lua will out-perform C++.

My final event evaluation setup evaluated events between around 1.5 and 15 times slower than the original setup. I had no illusions from the start that Lua would be as fast as C++. Taking a detour from C++ to Lua to obtain a result would then clearly be slower than taking the shorter road entirely within C++.

Using GlowCode to analyse the events I identified some of the main problems that makes the Lua setup slower.

I began by identifying common traits in groups of events that have similar relative runtimes compared to their C++ counterparts. What I found was that events that run almost as fast as the original ones spend most of their time in C++.

(24)

CHAPTER 3. RESULTS Events running much slower than the original however, spend a larger part of their time in Lua. This is interesting as all events share a common structure:

1. The event is prepared and called from C++. This includes pushing arguments like target information onto the stack and is identical for all events.

2. The event is evaluated by fetching one or more in-game values and possibly comparing them to some value. Every such fetch is a C++ call.

3. Result is returned to the C++ side.

Using pseudo code this would look something like this:

Preparation and Lua call // static overhead

for every line in event condition do:

fetch value from C++ side // done by calling C++ function

(compare to value) // not always done

return answer to C++ side // static overhead

As you can see there is some, more or less constant, overhead associated with each event evaluation as a whole and some overhead associated with each call to C++. Thus for an event to spend a larger part of its runtime in C++, the C++

functions used by the event has to be quite slow as the actual time spent in Lua is nearly constant for all events. This is wholly in line with my findings as the C++

functions used to fetch in-game values are practically identical to the ones used in the evaluation of the original events. The Lua events that run almost as fast as the C++ events are those whose fetch functions take such time that the Lua overhead becomes more or less negligible. By the same principle the Lua events that run much slower than their C++ counterparts, are events that are originally very fast.

This tendency can also be seen by sorting the event with respects to their runtime in C++. The increase in C++ runtime is followed by a lower relative evaluation time using the Lua version as shown in figures 3.3 and 3.4. As side note, an event that does only one fast fetch (like following a pointer) runs around ten times faster in C++ than using the Lua setup.

Usability

As described in section 1.1.2 using an embedded scripting language can reduce your workload by providing a more complete out-of-the-box solution than parsed scripts do. Implementing the EU3 event handling setup in Lua I realised that the process of creating new events was really simple once the functions querying the game state were in place. Lua would, if I would have wanted, allowed me to create all kinds of different complex events not possible to implement using the original setup.

14

(25)

3.4. RESULTS

Figure 3.3: Correlation between C++ runtime and relative runtime for country events

Figure 3.4: Correlation between C++ runtime and relative runtime for province events

(26)

CHAPTER 3. RESULTS

3.5 Conclusions

From my work I can draw two concrete conclusions:

1. Event handling using Lua embedded in C++ is not nearly as fast as using pure C++.

2. Working with an embedded scripting language such as Lua has it’s advantages compared to using parsed scripts.

As I previously stated the difference between parsed and embedded scripts lies in speed and usability and, as with all tools, you have to choose the one best suited to the task. If your main concern is speed you should probably use parsed scripts.

If speed is not that important embedded scripts are often easy to use and can save you a lot of development time. My Lua event handling solution was not as fast as I would have liked but at the same time Lua was easy to use and made the event setup easily extendible. I am sure that there are things that I have not yet tried and there just might be something that can make Lua events run almost as fast as the original ones.

3.6 Future Work

I have tried to determine how much impact the Lua system would have on the event evaluation process of a whole game session and my results point towards a sixfold increase.2 This is not very far from the desired outcome of three times or less that Paradox would have liked to reach to be able to use the Lua setup in their games. In this section I will discuss some ideas I have on how to further improve performance of a setup similar to the one described in this thesis and some ideas on how Lua can be used even where the demands on performance are not entirely met.

Storing In-game Information in Lua

If in-game information was stored in Lua instead of C++ the overhead from calling C++ functions would be removed. On the other hand, the whole process might be slower as it is now run in Lua. I have made some testing of this and runtimes are slightly improved and I believe this is a track well worth studying if you want to optimize your code. As Roberto Ierusalimschy writes in his paper [3] there is a trade-off between overhead and speed decrease. The trick is thus to find out which language what function should go in.

Complete Evaluation in Lua

Complete evaluation in Lua would require storing all in-game information in Lua.

It would however reduce the overhead for calling the evaluation function. This

2Testing could not be completed due to lack of time but included counting how many times each individual event was evaluated during a game session.

16

(27)

3.6. FUTURE WORK

approach suffers from the same trade-off as storing in-game information in Lua does but might generate better results. I have not had the time to try this.

Embedded Scripts for Prototyping

Using both parsed and embedded scripts simultaneously would clearly mean some code overhead but could also be beneficial. During development, an embedded scripting component could be activated giving scripters a better tool to customize and develop the scripts in terms of pure functionality. The scripts could then be translated into a format accepted by the parser and used as parsed scripts when the program is in production and the scripts no longer need to be tweaked.

This would of course add some overhead when maintaining the code base as both the embedded and parsed approaches have to be maintained but it also has the potential of greatly benefiting the scripters.

(28)
(29)

Chapter 4

Summary

In this thesis I have compared using parsed and embedded scripts in the context of Paradox Development Studio’s in-game event handling setup. The embedded scripting language used was Lua. The main focus of the comparison was the relative performance of the two different setups and a minor focus was how the development process could be affected by using the embedded script setup instead of the original parsed one. The evaluation was made by developing a prototype and evaluating its performance. Testing was performed continuously during development and im- provements were made according to the results. The final version of the prototype did not have as good performance as the original setup but on the other hand it offered improvements to the development process of new scripts and new scripting functionality. Runtimes of the Lua setup were between 1.5 and 15 times slower than the original setup and the major reason for this increased runtime was the overhead incurred by calling Lua instead of having all evaluation done in C++. Some ideas on how to improve runtimes have been presented including batching function calls and moving in-game information from C++ to Lua.

(30)
(31)

Bibliography

[1] Official GlowCode homepage. http://www.glowcode.com/, December 2011.

[2] Official Lua homepage. http://www.lua.org/, December 2011.

[3] Roberto Ierusalimschy. Lua Programming Gems, chapter 2. Lua.Org, 2008.

[4] Official Lua mailing list. http://www.lua.org/lua-l.html, December 2011.

[5] Official Microsoft Support page: How To Use QueryPerformanceCounter to Time Code. http://support.microsoft.com/kb/172338, December 2011.

[6] Mike Pall. www.luajit.org, December 2011.

[7] Lua users’ wiki on the uses of Lua. http://lua-users.org/wiki/luauses, December 2011.

(32)
(33)

Appendix A

Comparison of New and Original Event Files

A.1 A New Event

province_event[949] = {

trigger_prefix = [[ owner = get_owner(p)

controller = get_controller(p) function anp_func(p, anp)

o2 = get_owner(anp) return

controlled_by(o2, anp) and government(o2, 22)

end ]], trigger = [[

not ( controlled_by(p, owner) ) and not ( government(owner, 22) ) and government(controller, 22) and

any_neighbor_province(p, anp_func) and war_exhaustion(owner) >= 5 and

garrison(p) >= 1000 and has_siege(p, false) ]],

mean_time_to_happen_months = 300,

mean_time_to_happen_prefix = [[ owner = get_owner(p) ]], mean_time_to_happen_factor = {

5.0, [[ luck(owner, true) ]],

0.8, [[ has_owner_religion(p, false) ]]

} }

(34)

APPENDIX A. COMPARISON OF NEW AND ORIGINAL EVENT FILES

A.2 An Original Event

province_event = { id = 949

trigger = {

NOT = { controlled_by = owner }

NOT = { owner = { government = steppe_horde } } controller = { government = steppe_horde } any_neighbor_province = {

controlled_by = owner

owner = { government = steppe_horde } }

war_exhaustion = 5 garrison = 1000 has_siege = no }

mean_time_to_happen = { months = 300

modifier = { factor = 5.0

owner = { luck = yes } }

modifier = { factor = 0.8

has_owner_religion = no }

}

title = "EVTNAME746"

desc = "EVTDESC746"

option = {

name = "EVTOPTA746"

controller = { country_event = 747 } }

}

24

(35)

Appendix B

Test Code

open_debug_file();

int event_nr = args[1].GetInt();

lua_State *L = initLuaEnvironment();

try {

lua_getglobal(L, "load_province_event");

lua_pushinteger(L, event_nr);

lua_call(L, 1, 0);

}

catch( luabind::error e ) {

HandleLuaError( e.state() );

} catch( std::exception e ) { ASSERT_FAIL( e.what() );

}

AddLine( CString( "---Check province trigger---") );

int count = 0;

int nr_of_prov = CCurrentGameState::AccessInstance()->GetMaxNumberOfProvinces();

AddLine( CString( "Nr of provinces: " ) + CString( nr_of_prov ) );

CProvince *prov;

std::ofstream file;

file.open("timing_province.txt");

file << std::fixed << std::setprecision(5);

__int64 ctr1 = 0, ctr2 = 0, freq = 0, tot = 0;

for( int j=0; j < _nNrOfLoops; j++){

lua_settop(L, 0);

(36)

APPENDIX B. TEST CODE

lua_gc(L, LUA_GCCOLLECT, 0);

QueryPerformanceCounter((LARGE_INTEGER *)&ctr1);

for( int i=1; i < nr_of_prov; i++) {

prov = &CCurrentGameState::AccessInstance()->GetProvince(i);

if(prov->GetOwner().IsValid()){

try {

// calls the evaluation function in Lua

count += check_province(L, event_nr, prov, i, j);

}

catch( luabind::error e ) {

HandleLuaError( e.state() );

}

catch( std::exception e ) { ASSERT_FAIL( e.what() );

} } }

QueryPerformanceCounter((LARGE_INTEGER *)&ctr2);

tot += (ctr2 - ctr1);

}

QueryPerformanceFrequency((LARGE_INTEGER *)&freq);

file << "Average " << (tot * 1000.0 / freq)/_nNrOfLoops

<< " msecs in lua (old style)\n";

AddLine( CString( " " ) );

AddLine( CString( "Lua event version (old):" ) );

AddLine( CString( "Average time over ") + CString( _nNrOfLoops )

+ CString( " loops was " )

+ CString( (tot * 1000.0 / freq) / _nNrOfLoops ) + CString( " msec." )

);

AddLine( CString( "Nr of true triggers: ") + CString( count ) );

//AddLine( CString( "Lua mem use: ") + CString(lua_gc(L, LUA_GCCOUNT, 0)) );

// CODE FOR TESTING ORIGINAL EVENTS

//***************************************************************************

CID id(ID_TYPE_EVENT, event_nr);

CEvent *pEvent = (CEvent*)CEvent::GetObjectFromID(id);

26

(37)

CSimpleRandom Random;

CEventScope scope(Random.GetInteger());

scope._Country = CNullTag();

CProvince * pProvince;

count = 0;

tot = 0;

for( int j=0; j < _nNrOfLoops; j++) {

QueryPerformanceCounter((LARGE_INTEGER *)&ctr1);

for( int i=1; i < nr_of_prov; i++) {

try {

pProvince = &CCurrentGameState::AccessInstance()->GetProvince(i);

if(pProvince->GetOwner().IsValid()){

scope._nProvince = i;

bool trigger_true = pEvent->GetTrigger().Evaluate(scope);

if( j == 0 && trigger_true ) count++;

} }

catch( std::exception e ) { ASSERT_FAIL( e.what() );

} }

QueryPerformanceCounter((LARGE_INTEGER *)&ctr2);

tot += (ctr2 - ctr1);

}

QueryPerformanceFrequency((LARGE_INTEGER *)&freq);

file << "Average " << (tot * 1000.0 / freq)/_nNrOfLoops << "\tmsecs in C++\n";

AddLine( CString( " " ) );

AddLine( CString( "Original event version:" ) );

AddLine( CString( "Average time over ") + CString( _nNrOfLoops )

+ CString( " loops was " )

+ CString( (tot * 1000.0 / freq) / _nNrOfLoops ) + CString( " msec." )

);

AddLine( CString( "Nr of true triggers: ") + CString( count ) );

AddLine( CString( " " ) );

(38)

APPENDIX B. TEST CODE

// ENDS HERE

//****************************************************************************

file.flush();

file.close();

lua_close(L);

close_debug_file();

28

(39)
(40)

TRITA-CSC-E 2013:019 ISRN-KTH/CSC/E--13/019-SE ISSN-1653-5715

www.kth.se

References

Related documents

Our aim is to code player statements in a way that allows us, during analysis, to determine whether a player encountered pillars of reflection (according to Khaled, 2018), engaged

This article hypothesizes that such schemes’ suppress- ing effect on corruption incentives is questionable in highly corrupt settings because the absence of noncorrupt

Through the use of Lua as a complement to C and the message passing semantics of the actor model in the design of a embedded touch display system, we have found that this approach

A focus group was conducted after the participants had used their tools for a fair amount of time to ascertain their authoring process experience2. In the end, the participant using

This project explores game development using procedural flocking behaviour through the creation of a sheep herding game based on existing theory on flocking behaviour algorithms,

This section presents the resulting Unity asset of this project, its underlying system architecture and how a variety of methods for procedural content generation is utilized in

6 Please note that no attempt was made to determine the mother tongue of the participants. Therefore, some of these spellings may be related to languages other than English.. also

For fixed main axis size containers, we can specify the alignment of items across the main axis (start (default), center, end, space-between, space- around).. This is the handling