Main Page » My Games » Villainmad »

2011/02/28 (Villainmad): Changes!


Always use secure-HTTP / Secure HTTP / Permanent Link

So, I haven't resolved the issue from last time, and given that the problem is clearly " doesn't like being called 150 times in a single frame, much less 500" and/or that the information therein is bouncing back and forth between Lua and C#, I'm frankly not sure solving it is even possible without altering LuaInterface's source; anyway, it's generally best to deal with bullets in bulk instead of individually,1 whereas it doesn't matter as much if you have enemies using it, since it's rare to have more than i.e. ten enemies onscreen at once, I guess. The game doesn't even seem to notice the single object using Draw() to do the background, at any rate. So this article is mostly going to be about unrelated stuff.

First, Sixten is unfortunately unable to continue working on Villainmad's art assets due to his own RL-and-other commitments, so Shintear is going to be doing it instead. Here are his current drafts of the two player characters, Lexy Striker (biker babe bitch) and Hiyasu Ikoma (android Emotionless Girl):

ScrollingClouds ScrollingClouds

Second of all, I am adding a new value to the GameEntity structs: Rotation. Things Which Are Able To Rotate is a longstanding video game tradition, both for things which spin and things which point in the direction they are moving in. I discovered that I really didn't have any feasible way of handling this in the current setup, so, I made one!

In fact, all I have left at this point is to make user-input, spawning the player, creation of UI-objects (score, boss-meter, dialogue, etc), and menus (which will be an entirely different creature and exist outside the stage), and a little bit more sound-and-music stuff, and that'll really be it for the base engine.2 Oh, and documenting everything, of course. All I'll have left to do is making the actual game.

1Random thought: if you want to change the appearance of ALL THE BULLETS, you could just change the texture on the controller-table. Hmm ... maybe have a stage.Update() function, too! Could do all kinds of things there ... Sure, let's do that!

2Making it so you can have 3D-backgrounds will be a much later, longer-term goal, and perhaps outside the scope of Villainmad.

25 Comments (auto-closed) (rss feed)


sounds quite hard.. im not that good at programming yet, but sounds logical. so uhm keep going :3 I bet you'll be able to claim the 'epic' state when youre done.


So does this mean you have found a bug in your LuaInterface itself (memory leak, race condition, GC not triggering when it should, or something else)?


Ah, it didn't quite sink in last time that you were calling so much. 150 times a frame (multiple frames per second) between two runtimes? Ouch.

C# and Lua both need runtimes to work, and they are incompatible. Thus a call to Lua code from C# does not act like a call to C# code from C#. A context switch has to be performed to save the state of the C# runtime as the program switches to the Lua runtime for the call, and another context switch is needed when the Lua runtime switches back to the C# runtime for the return.

Context switches are not cheap. Not by a longshot. First off, they require enough of the CPU state to be saved that when the execution returns from the context switch, the program acts as if it's never been interrupted. Second, they're done by the OS, as its part of the multitasking architecture, so you're restricted to the modes availible in context switching. Worst case is if a full process switch is necessary, as usually the OS only performs those in the low thousands per second. (You probably are doing thread switching, which is not quite as bad.) And the OS needs to perform its own context switching for its own housekeeping.

Ballpark calculations put the number of context switches required by your scheme at around 9000 per second for 30 fps. Your OS may be limiting the number of context switches allowed by your program, so you should check to see how many context switches your program is making. Hence, slowdown.

Dizzy H. Muffin

Yesh, update() was being called once for each individual bullet, and there were 150 bullets onscreen. So I'm doing without, as far as bullets themselves are concerned~ (And even worse, it's actually 60 frames per second!)

Meanwhile, I've discovered the most fascinating and incomprehensible bug ever! Background: I set up Lua-side getters and setters for the GameEntity values, so that within Lua you can do i.e. SetVel(ObjID, Vector2(3,0)) to cause the object to move directly to the right at three pixels per frame; QueueCommand(delay, function), which is how you do delays; and I coded up QueueFunction(delay, func, ...) which is basically MWChase's implementation of "thunkify()" except it feeds it directly into QueueCommand instead of returning it. With me so far?

Okay, so! If you do SetVel(ObjID, Vector2(0,0)) (which translates to "set this object's speed to a Vector2 with both X and Y equal to 0" — in other words, stopping it in its tracks), it works fine. If you do QueueFunction([whatever], SetVel, ObjID, Vector2(3,-3)) ("[whatever] frames from now, set it to diagonally up and to the right, 3 pixels per second each"), it works fine. But if you do QueueFunction([whatever], SetVel, ObjID, Vector2(0,0)), it somehow deletes the object. I have no idea how or why it is doing this. So I set up a StopObj(ObjID) function, which does the same thing~


Eesh, that sounds significantly worse than the kinds of numbers I was getting back when I started. I can't say anything about now, though, since the most I've done lately is standardized the interface and gone "OH GOD, SCHOOLWORK".

And then this goes on the back burner for NaNoEdMo. And then I've got another random project that has nothing to with anything, after which I'll either work on this or outline this year's NaNoWriMo: Total Eclipse of the Man. (It is a romance novel that had me thinking about biomass pyramids.) I... may go into a coma when I graduate next year.


Only one solution to Muffin's disappearing object problem: get out yer debugger and trace yer program! You need to verify that the individual steps of your code are doing exactly as you intended.

Dizzy H. Muffin

/ Modified by Dizzy H. Muffin:

Okay, fooling around with "Trace.Assert()" ... it is deleting it because somehow, instead of being set to Vector2(0,0), it is being set to Vector(256,744). Which is to say, it is going flying off vaguely towards the upper-right at over 47000 pixels per second, taking it right out of the playing field and into auto-delete-land.

EDIT: Both when I instantiate new Vector2(0,0) and when I point at Vector2.Zero, for the record.


What are you doing for the music? Are you making an original soundtrack?

Dizzy H. Muffin

'Course I am. :3

Dizzy H. Muffin

It also just occurred to me that I could animate things in a much less convoluted manner via an entirely-C# "animation" class/struct!

And also that I forgot about implementing replays in my "almost done" dealy. (Which will also entail coming up with my own random-number-generation methods ...)


So you construct a zero Vector2, and you get a vector that has a large value? Does the call to the Vector2 constructor, Vector2(0,0), give you this (256,744) Vector2 value immediately, or does it change later in the code?

Dizzy H. Muffin

It changes later, somewhere in the thunkification. (I imported System.Diagnostics into Lua! ^^) All other uses of Vector2.Zero or Vector2(0,0) in Lua work properly.

I'm not sure how you'd even check, beyond that point ... the LuaFunction C# type is kinda opaque.


(Which will also entail coming up with my own random-number-generation methods ...)

having a problem with the random number generation?

and for maximum random, use a Mersenne Twister!

Dizzy H. Muffin

Nah, I meant "method" as in "public double Randomnumber()" kinda thing. The "problem" is replays: you need to have a method which does "Assign this random-number-generation an index. If This Is Not A Replay, randomly generate a number and assign that index to it. If This Is A Replay, get the previously-randomly-generated number at the specified index." You do NOT want a replay to do anything differently from the original.


Couldn't you just use a deterministic random number generator (one that whose output for its lifetime is entirely determined by its initial seed(s))?
Then, when saving a replay, you save the random seed in use for that game. Them, as the replay progresses, every-time the random number generator is asked for a new random number, it will give the same one as the original play-through.


Yeah that's all you need to do. The same random methods will generate the same numbers with the same seed, so you just need to store the seed

Dizzy H. Muffin

Kay. It looks like Lua's RNG is pretty deterministic, anyway. I thought I was overthinking things ...


So, wait... are you saying that the Vector2 goes into the LuaFunction with (0,0), and immediately after calling it, in Lua just after the Lua code you wrote gains control of it, it's this bizarro value?

If that's the case, then it may be that the C# Vector2 type does not translate well into the Lua Vector type. It may need to unwrap the Vector2 and pass in the floats individually.

If not, and the C# Vector2 becomes a Lua Vector, then trace the Lua code to make sure the Lua code is behaving correctly as well.

Dizzy H. Muffin

I'm not sure about "immediately," but the short answer to your question is "yes." Via abuse of Trace.assert(), I've determined that within the thunkify function, it's still a Vector2.Zero immediately after it does local args = {...}, but it obviously isn't by the time the function is actually executed. I'm not sure how to actually get at the data between those two points.

It also works just fine if I queue a Vector2 with any other value, i.e. Vector2(3,0). (For SCIENCE, I created "SetVel2" and "SetPos2" functions, which take the floats separately, and QueueFunction(delay, SetVel2, obj, 0, 0) works per specifications, but that's entirely on the C# side of things, so yeah.)


How very odd! I admit, I'm stumped why this only misbehaves on (0,0) and not any other value.

As to fixes, I may have several, ranging from hackish as hell to not quite elegant, but not too messy either.

The first depends on the Vector2.Zero value always translating to a Lua (256,744) — that if the Lua code sees this bizarro vector, just arbitrarily replace it with a (0,0) vector. The problem is that this is (as I said) hackish as hell and is probably not portable.

A slightly more portable but still inelegant solution is to add a isZeroVel parameter to your Lua function, where the actual value of the passed vector is ignored and (0,0) substituted instead. Workable, but ew.

Third is to see if very small floats will translate properly, and then replace the first member of a proper zero Vector2 with Vector2(VERYSMALLFLOAT,0).

The forth is to bite the bullet and consult the documentation on LuaFunction to see what data types are guaranteed to be accurately translated (and passed back) and stick to them religiously. You develop a protocol of how a Lua function is to recognize a C# Vector2 in this form, and automatically handle the rewrapping into a Lua Vector and vice versa.

PS, I have to ask, because it's not quite clear that you've done this, but are you quite sure that the Lua side is getting this bizarro vector? You have the Lua equivalent of Trace.assert() saying that we have this bizarro vector before we've done anything else in Lua, right?


IIRC, to get at the data between storage and execution, you need to probe delicately at its innards with debug.getupvalue(thunk, index), where index is probably either 1 or 2. Probably.

Dizzy H. Muffin

I have verified that it is (256,744) both in C# and in Lua. (Be nice if you could set up a Lua version of Debug.Assert() which allows you to continue after throwing an error if you choose "ignore", but ...)

I actually tried the "verrysmallfloat" trick, but the problem with that is that it messes with objects which use their movement to determine their rotation; an object which had previously been pointing down and to the left as it moved would abruptly start pointing straight up. (I also tried a value which was below the minimun "float" value but within the minimum "double" value, and it just treated it as 0,0.)

I'm not going to try the "isZeroVal" function, because 1. the primary purpose of "SetVal" is to give it an arbitrary other velocity, and 2. I've already got a StopObj function which simply does "set the object's velocity to 0" without any other considerations.

I am looking into what it would take to reinvent Vector2 (and various other things) within Lua, and then converting the various C#-side functions into ones which work with the new system.

I suppose as a last resort, I could scrap Lua and use CS-Script instead, avoiding the "two different languages" thing entirely, at the expense of ... well, take your pick, I suppose.

Dizzy H. Muffin

Though now that I actually am seriously considering using C# as the scripting-language, I'm stuck on figuring out where do I even begin. I mean, yeah, there's innumerable places that tell me how to do "Here is script-file; compile and load it at this point in runtime! Oh, want to compile it instead? There's that, too!" but I'm not sure how to create separate stages/games, loading one file and unloading another ... I mean, yeah, I know how to do "curStage = null; curStage = new Stage(whatever); and the like, but that's a separate thing, it's more how do I define a stage/game in the script-file, so that they can be loaded separately and don't conflict with each other (i.e. a class with the same name)? Shold you give a class-type a different name (i.e. "class ExtraStage : Stage"), or should you somehow make it an instance of the base Stage class? Can you unload a C#-scriptfile and its data-types after having previously loaded it?

I'm not really finding any comprehensible answers because just trying to figure out what the right questions are (and how to phrase them) is giving me a headache.

Dizzy H. Muffin

And I have, in fact, managed to figure everything out!

The trick was, in fact, to ask the right questions, or at least use the right Google terms.


The trick was, in fact, to ask the right questions, or at least use the right Google terms.

I'm sorry, my responses are limited. You must ask the right questions.