Main Page » My Games » Villainmad »

2011/02/19 (Villainmad): Yes It Matters

Created:

Always use secure-HTTP / Unsecure HTTP / Permanent Link

I don't like to ask for help, but for some reason I don't mind complaining and seeing if anyone offers any.

—Scott Adams

So, I'm almost where I was in December, except that I haven't added the code for the player. I haven't gotten around to adding the collision-detection code yet, because I've run into (har har) another issue: there is a difference between doing something in Lua and in C#.

For the record, here's the entire "if the object's LuaTable has an Update function, execute it every frame" code:

// curObj and curEnt are respectively the "object-ID" and "game-entity" structs I described in the last entry.1
if (curEnt.Table["Update"] != null)
{
    // There does not seem to be any other way of doing this besides creating a new "LuaFunction" object, casting the element from the LuaTable, AND THEN doing the .Call() method from that.
    LuaFunction caller = (LuaFunction)curEnt.Table["Update"];
    caller.Call(curObj, curEnt);
}

I noticed this when I tried to run the "if the object is out of the game-field, delete it" code in the Update() function, and discovered that the game was gradually slowing to a crawl. Well ... not "slow to a crawl," exactly, it was skipping a bunch of frames.2 After enough time had gone by, it was starting to run in seconds-per-frame instead of the other way around. With the aid of SlimTune, I determined that the primary resource-hog was the "LuaFunction.Call()" method. When I moved the "if it's out of the field, delete it" to the C# and got rid of the Update() function, the game ran perfectly fine.

Now, this happened even though the current test-script never has more than about 60 or so bullets at once, and even when the Update() function consisted solely of "return false." It also happens with both Update() and Draw() (when I tested it with that). Now, this isn't that much of a problem for bullets, which you're generally going to be handling in bulk and using Sequence-objects, but more complicated things like enemies and suchlike, or even things like Kogasa's "fade from one color to another" bullets ... yeah.

So, in lieu of any idea of how to make LuaFunction.Call()-from-C# take up less processing-time, after some thinking3 I've come to the conclusion that the way to do this is to send the list-array of objects back into Lua, and have it iterate through and update all the objects, on the basis that two LuaFunction.Call()s (one for "update" and one for "draw") is easier on the CPU than 100. The problem with that, of course, is how to get an array and/or a list into Lua. Not sure how well it would work with Lua's luanet.import_type(), either. (Random thought-which-just-occurred-to-me for how to speed up the LuaFunction.Call(): Is it because I'm passing two C# structs as the arguments?) I must do SCIENCE4 to figure this out!

1LuaInterface lets you use the functions luanet.load_assembly() and luanet.import_type() to use C# (by which I mean .Net) object-types within Lua.

2I'd rather figure out how to get XNA to slow down instead, but ...

3And false starts! For instance, while I was typing "handle it in bulk" above, I thought: "Hmm ... now there's an idea! Instead of calling Update() once for every object, have it call Update() only once, and have it iterate through every object instead! ... mmm ... no, wouldn't work. Too hard on the logistics, and it'll unerringly deal with things in the wrong order."

4By which I mean, methodical experimentation.

11 Comments (auto-closed) (rss feed)

sRc

If I cared more for Lua I would be of more help when you make those complaints, but I don't (and never really have) know or care much about Lua

John Evans

I don't know enough about the C#/Lua interface to know if this will help, but...Are the parameters passed by reference or by value? Does the Lua part have to create Lua versions of the objects each time they're passed through?

Dizzy H. Slightly Voided

I am not entirely sure whether it's reference or value.

I do know (after some testing) that the problem seems to be solved by replacing the use of the ObjID and GameEntity types with basically all their values (layer, position-in-layer-list, object's position, etc) as separate variables. Examining further, it only seems to cause slowdown when it uses the GameEntity and ObjID object-types at all. (Roughly the same amount of slowdown when it uses GameEntity, occasional twitching when it only uses ObjID, none at all when it uses neither.) Which means that the fault is with the Lua-interface between Lua and those two types.

I'm gonna have to figure out how to deal with that stuff when I get up tomorrow ...

Dizzy H. Slightly Voided

Okay, having gotten up tomorrow, and then pondered and poked a bit ... I'm pretty sure the problem with the GameEntity is the Lua Table pointer. Using the "GameEntity" type in Lua ... it has to have a pointer to the "LuaTable" class in C#, which then points back to the table in Lua, in some kinda pointer-double-reacharound.

So now I've changed it so that it uses All The Values, but also uses its own Lua-only functions called ObjID() and GameEntity(), which simply return ordinary Lua-tables which are roughly shaped like the Structs were, so to speak. Unfortunately, there's still an occasional slight stutter; I've figured out that this isn't being caused by the use of the C# ObjID-type, it's being caused by the LuaFunction.Call() itself. My current theory is that it is from the mere act of passing a LuaTable from C# back into Lua.

We're gonna need more SCIENCE.

Shintear

wah...the code is hurting my brain...and I'm not even trying to understand it.
Thank god I'm in the visual field...

John Evans

Sounds like you're making progress, though. :)

Wyrm

Ah, the perils of working with two languages at once. Getting the two to play together nice-nice is always a royal pain in the fucking ass.

What was the non-working version of this code, just for comparison?

Dizzy H. Slightly Voided

Non-working version of which? The ObjID struct was just three integers (CreationFrame, Layer, and ListPosition), and the GameEntity was an int (CreationFrame), two Vector2s (Position and Velocity), and a LuaTable (yeah), with constructors for each. And the code I posted was the "non-working" version.

Another idea which I'm toying with: store the "GameEntities" in Lua, and have C# merely tell Lua to execute the apropriate function for the specified element (using two numbers). C# would thus only have to put a LuaTable back into Lua when creating the object. Which could have its own problems when creating a bunch of objects at once, but we'll see how that goes ...

H0lyD4wg

First version: a program that starts fast and gradually becomes slower over time is in many cases a symptom of a memory leak. Check its memory consumption; if it's always increasing, that's a strong indication that you have a memory leak on your hands.

Second version: a slight stutter that only happens once in a while sounds very much like a garbage collection pause. Historically, GC pauses like that were one of the main reasons that game programmers have avoided garbage-collected languages.

If garbage collection is really the reason for the stutter, that doesn't mean that you have to re-write everything in C (writing anything in C is usually a very bad idea); there are ways to get around that.

First, figure out which garbage collector is responsible. Both C# and Lua are garbage-collected, so either may be the culprit. Then you want to make garbage-collection incremental, so that you spend a small amount of time collecting garbage every frame instead of not doing any collection at all for a while and then doing a single big one that forces you to skip frames when too much garbage has accumulated.

In Lua you can call collectgarbage('step') to trigger such an incremental garbage-collection step. I don't know what kind of API C# provides for controlling garbage-collection.

Dizzy H. Slightly Voided

Hmm. Now that you mention garbage-collection, the stuttering and slowdown does seem to correspond somewhat directly to when the bullets start deleting themselves —

Oh. No, I've done a few tests, and the super-slowdown starts before any of them are deleted, if there's enough of them on the screen at once, so I don't think it's due to that. Although it does seem to handle the "draw" function much better than the "update" function.

Anyway, the code in Lua is:

function stage.ball.Update(objid, ent) return false end
function stage.ball.Draw(objid, ent) DrawObj(ent.Table.texture, ent.Pos, Color.white) end


(DrawObj() is a registered-C# function which does an ordinary SpriteBatch.Draw(), which is an XNA-specific dealy.)

If I comment out both of those lines, it works perfectly fine, full 60FPS, no slowdown. If I uncomment the Draw one, it staggers and twitches a bit. If I uncomment the Update one, it screams and screeches to a halt and goes "WHY-A YOU DO THAT TO ME!?" and skips 50 frames at a time. Also, neither collectgarbage('step') in Lua nor GC.Collect(); in the C#'s main Update() seem to have any effect on this whatsoever. So yeah ...

Dizzy H. Slightly Voided

/ Modified by Dizzy H. Slightly Voided:

Right before bed, a thought occurs to me (not that I'm sure of whether it is useful in this particular instance (EDIT: Nope, doesn't fix anything)): I really don't need to pass the entire contents of the GameEntity struct to the Draw and Update functions; it only needs Position and Velocity. Why? Because the other two variables in GameEntity are 1. a number which is already included in the ObjID struct, and 2. a pointer to the LuaTable which contains the Draw and Update functions I am in fact passing this data to.

SOMETHING TO LOOK FORWARD TO TOMORROW!

(OTHEREDIT: though actually, it would probably be helpful if you wanted to do like some object-oriented stuff. So never mind~)