Main Page » My Games » Villainmad »

2011/01/25 (Villainmad): Reboot!

Created:

Always use secure-HTTP / Secure HTTP / Permanent Link

"Hey, Muffin, where's Villainmad been?" I am glad you asked, imaginary person who asks questions! The answer is that I have been 1. wrapping my head around Lua, and 2. basically starting the programming, if not from scratch, then certainly from sniff. (You may also have noticed that Create.swf Adventures is taking up a considerable amount of attention, too.) Another distraction is that it seems like every third time I start doing some Major Coding, I discover that I'd been spending like an hour doing entirely the wrong thing, because I'd overlooked some miniscule detail that was causing things to Not Work.1 That's cool, though; it means I'm catching that sort of thing early on. And it's not like I'm being paid for speed or anything ...

Anyway, my biggest challenge at the moment is figuring out exactly how I'm going to get "queue commands" from the Lua system into the C# system. The Easy Way™ is to implement LuaCode that looks something like:

QueueCommand(6, "stage.DoTheSpinnyThing(5)")

which means "Execute that string six frames from now" without bothering itself with whether "stage.DoTheSpinnyThing(5)" is valid. (I'm going to decree that all variables and functions which are specific to a given stage (i.e. non-global) must belong to a Lua table named "stage" to make it easier to get rid of everything stage-specific at the end of the stage.) On the C# end of things, it stores "(current number of frames since stage began) + 6" in as integer and "stage.DoTheSpinnyThing(5)" as a string, and when the time comes, it goes, "Hey, Lua! Execute this string!" I suppose that works as well as anything else, and it isn't all that convoluted. You win some, you lose some, but I welcome suggestions from people who are more experienced with getting information between C# and Lua.

Now, the other thing is, Lua doesn't know object-oriented programming, but if you hum a few bars, Lua can definitely fake it. The problem, however, is this: how do you get it to point at this particular instance of the new object? The answer which immediately came to mind is that, when constructing a new object, you use the name of the variable to which you are constructing it in the instance itself! So you'd do something like:

--The second agument is named "this"
stage.spoonerism = SpinnyThingObject:new(9, "spoonerism")

and then, within one of the object-functions itself:

spoonerism = QueueCommand(6, "stage."..self.this..":SpinOnce()")

And it will work just fine! See? It makes PERFECT sense! (Warning, link leads to TVTropes. Muffin will not be held responsible for hours of time lost as a result of following it.But see also this disclaimer)

... I need to start this whole plan over. It's a good thing I haven't actually coded any of this yet ...

1For example: I had a class for controlling "the current game." It had both a variable in the main Game class, and it had been added to the Game class's List Of Components. When I wanted to run the "start a new game" code, it created a new instance onto the variable and added it to the list of components ... but I didn't remove the old instance from the list of components, which meant that the system wasn't actually getting rid of it.

13 Comments (auto-closed) (rss feed)

sRc

good to hear you're getting things figured out, was starting to wonder when your twitter was getting eerily quiet about the subject :)

John Evans

Programming involves a lot of sitting there wondering why something isn't working. Eventually, if you're persistent enough, you figure it out and get a little better. So, you're not doing it wrong. ;)

And, that is how object-oriented programming works, pretty much. Each object has a pointer to itself. Classic stuff. ;)

Dizzy H. Muffin

Well, yeah, but the "this" variable isn't a pointer to itself (that's "self"), it is a string with "name of the global variable which is a pointer to this particular instance."

Mostly, I instinctively kinda feel like there ought to be a better way, but ...

TechSY730

I am assuming you are spending a lot of time getting the API, both private and public, well designed, flexible, and moderately easy to use?

That is a VERY hard task to accomplish, and making even a little mistake in "lower" level parts can have MAJOR design repercussions the the "higher" level parts.

Keep at it though. Getting a good foundations may be tough and sometimes mind bending, but it is by FAR worth it once you finally get a good foundation down. Many programmers know how awful it is trying to code around a rushed API. I commend you for taking your time to avoid inflicting that pain on others (or yourself).

Dizzy H. Muffin

Yeah, I can safely say that most of my time and effort is being spent rewriting and rearranging code I'd already written. ;)

MWchase

One thing I realized after writing a whole bunch of functions that reconstruct a conceptually persistent thing with each call (In other words, they set stuff up, operate on it, and then garbage-collect it, every single frame) is why coroutines are important. I should (should!) be able to rewrite everything that uses my probably-sort-of-wonky "tick" setup so that it only has to set things up once. I don't know if you've got enough of the code living in Lua for this to remotely make sense, but ever since I realized that everything could be an infinite loop, I've been telling everyone because WHOA COOL.

Like I said, I don't know if this makes sense for you, since we're doing things quite differently (mine is functions in Lua that get called by code in C++, yours sounds like functions in Lua that call code in C#, which may then... call code that you wrote in Lua?), but, well... it's neat! All of the code thinks it's the only thing that's running, and everything else is happening in its function calls! If you model bullet movement in Lua, rather than C#, this might be something to look into.

(Oh, also, I forget: are you doing this in Mono-compatible C# stuff, or no?)

sRc

Yeah, I can safely say that most of my time and effort is being spent rewriting and rearranging code I'd already written. ;)

hooray, we'll make a real programmer out of you yet! :)

(Oh, also, I forget: are you doing this in Mono-compatible C# stuff, or no?)

working with SlimDX and XNA its not going to be Mono compatbile (although I havent ever looked, can Mono interface with WINE for DX processing?), but long as he's using functions wrapped around the graphics calls instead of the graphics calls directly in his calls then it wouldnt be hard to port later, just plug in somethine else

lets not try to put everything on the plate at once, getting something running the right way in one platform is hard enough without bringing cross-platform into the mix ;)

H0lyD4wg

All this mucking around with strings is no good. It's easier and cleaner to use thunks (anonymous functions that take zero parameters) instead. For example:

QueueCommand(6, "stage."..self.this..":SpinDegrees(90)")

Turns into:

QueueCommand(6, thunkify(self.SpinOnce, self, 90))

Where thunkify is defined as:

function thunkify(f, ...)
local args = ...
return function ()
return f(unpack(args))
end
end

Thunks created this way will keep referring to the correct objects no matter what you do to the global variables after you create them, unlike the string-evaluation based method that can be easily (and inadvertently!) tampered with by unrelated code.

Dizzy H. Muffin

/ Modified by Dizzy H. Muffin:

Ah, I knew I'd get the attention from someone with a better idea of how this works than me. ;)

Yeah, I think I'll use the "thunkify" function (with a name that will make more sense to end-users). Thanks for the suggestion! I'll have to experiment with this, to see what mad science I can come up with, GYEEHEEHEEHEEHEE! (Also, uh, I'm not using SlimDX anymore, it's not "easy" the way XNA was ...)

EDIT: Okay, uh ... the arguments don't seem to be getting where they're supposed to be going ... I've got:


function Stage.Main:Speaker(stuff)
--console(): A function which writes the given string to the C# console, prefixed with "mew> "
console(stuff)
end
function Stage.Main:Thunker(stuff)
--thunkify(): A C# function which takes argument "LuaFunction thunk" and then does "thunk.Call();"
kerthunk(thunkify(self.Speaker, self, stuff))
end


but when I test it with lua.DoString("vec:Thunker(\"b'duh\")");, the line is just blank after the "mew>" ...

Bossman

If you're using H0lyD4wg's thunkify, then what happens is this:

thunkify(self.Speaker, self, stuff)

calls this:
self.Speaker(self, stuff)
but your Speaker function only takes one argument. It drops the string you wanted to print and tries to print your Stage.Main, which it can't do since that's a table. Drop the self and it should work.

Dizzy H. Muffin

*right before going to bed* With some Science, I have determined that yeah, the last argument seems to be just disappearing. And I basically copied and pasted H0lyD4wg's thunkify function, yeah.

Bossman, Lua functions which are in tables actually work two different ways: if you do SomeTable:SomeFunction(blah), that is the same thing as doing SomeTable.SomeFunction(SomeTable, blah) (note the period instead of the colon), and both of them create the "self" pointer. (I am explaining this really badly because I am typing this instead of sleeping)

Anyway, if I remove either "self.Speaker" or "self", it complains that unpack() was looking for a table, not a string. If I'm getting this right, the first argument in thunkify() is the Function You Want To Call, the second is "self," and the rest are supposed to be the arguments for that function.

Also-also, it is not clear what happens if you try to do thunkify() on a function which is not a part of the table. I mean, technically, most functions would all be in the "stage" table (congratulations you've created a pointer for the entire stage just so you could get at a single function in it), but what if you wanted to create a "universal" function to be used throughout the whole game ...?

Blah I'm just going to bed I should've saved this for morning

MWchase

Poking the interpreter some. It looks like the issue is with unpack.

No, wait, got it. It should be

function thunkify(f, ...)
local args = {...}
return function ()
return f(unpack(args))
end
end


Hooray for reading the manual. Also, hooray for preview, or I would have looked really stupid. Anyway, I think that's the easiest way to handle things for varargs. Taking anything out makes the interpreter complain at me.

Dizzy H. Muffin

Huzzah, that worked!

Also, it works fine with functions that aren't part of tables, you just have to leave out the "self" argument.

Thanks a million, MWchase and H0lyD4wg!