Context-switching/"soft threads" in scripting languages?

A place to discuss the implementation and style of computer programs.

Moderators: phlip, Moderators General, Prelates

commodorejohn
Posts: 1108
Joined: Thu Dec 10, 2009 6:21 pm UTC
Location: Placerville, CA
Contact:

Context-switching/"soft threads" in scripting languages?

Postby commodorejohn » Fri Nov 13, 2015 8:28 pm UTC

Not a terribly indicative thread title, but it basically sums up what I'm trying to accomplish. I've been looking at doing a game project of late, which will require that game objects be able to independently execute arbitrarily complex actions (i.e. not just a simple state-machine behavior like, say, Pac-Man) over arbitrary periods of time. I was initially looking at using PyGame, because it would be fairly simple to use Python itself for the object scripting, but I've looked at a couple other alternatives, and I keep hitting the same stumbling block: standard procedural languages just don't support arbitrarily pausing and resuming execution within a function without bringing the whole program (or at least the current thread) to a halt.

To give an example, let's say I want character A to walk to point B, wait 15 ticks of the game's internal clock, and then walk to point C. The natural, intuitive sequence of instructions for this should look something like:

Code: Select all

function doThing() {
walk b.x, b.y
wait 15
walk c.x, c.y }

But the whole program can't come to a halt for 15 ticks, because there's other stuff that needs to be happening (animation updates, other characters doing stuff, etc.) And the language (pretty much any that I've looked at) doesn't support just breaking off in the middle of a function to come back to it at a later point. So instead I'd have to break this up into separate functions and update some kind of next-action pointer at the end of each one from a list, so I'm creating some kind of meta-script that scripts scripts:

Code: Select all

function doThingA() {
walk b.x, b.y
self.next = doThingB
wait 15 }
function doThingB() {
walk c.x, c.y
self.next = doAnotherThingA() }

[...]

That quickly gets ridiculous as the actions get longer and more complex. Even having anonymous functions only alleviates the need for huge numbers of names cluttering up the namespace; there's still the tedium of breaking up naturally-flowing scripted events into individual little script-atoms. But I'm not seeing a significantly better way to do this in the context of standard structured procedural languages (i.e. pretty much anything that falls under the "general scripting/programming language" category these days.) And God knows I don't want to delve into full-fledged threading just for something this simple!

It's frustrating, because I've used game-creation systems that can do this naturally as breathing (ZZT and MegaZeux both allow you to do the first example almost verbatim,) but of course those systems implement their own particular scripting language that isn't available separately. It's gotten to the point where I'm seriously considering (and have started designing/coding) a simple virtual-machine architecture to execute a proprietary scripting language much like those systems provide. That seems overly complicated as a solution, but no less so than trying to thresh individual little kernels of functionality out of what should rightfully be perfectly straightforward action scripts. I'm just curious, can anyone think of a better solution? Is there a scripting language out there that supports this kind of thing without a lot of bother? Or should I just keep rolling on creating one that does?
"'Legacy code' often differs from its suggested alternative by actually working and scaling."
- Bjarne Stroustrup
www.commodorejohn.com - in case you were wondering, which you probably weren't.

User avatar
Flumble
Yes Man
Posts: 2049
Joined: Sun Aug 05, 2012 9:35 pm UTC

Re: Context-switching/"soft threads" in scripting languages?

Postby Flumble » Fri Nov 13, 2015 9:46 pm UTC

But... how do you even tell the language that you want to initiate (forking in terms of threads) a sequence of actions and not be stuck in its flow? You know, when you want to create two characters at once who walk (and wait) around. :o

commodorejohn wrote:To give an example, let's say I want character A to walk to point B, wait 15 ticks of the game's internal clock, and then walk to point C. The natural, intuitive sequence of instructions for this should look something like:

Code: Select all

function doThing() {
walk b.x, b.y
wait 15
walk c.x, c.y }

Note that walking isn't an instantaneous action*: it's in discrete steps (or it's continuous but the snapshots are discrete) for every frame. Even worse: there could be events that require the walking to stop somewhere along the way (for example: being killed).

Most languages indeed don't automagically transform (pre-defined) long-term actions into discrete steps. There's a game/event loop in every game engine though, so you "just" have to keep track of a list of actions to be performed (walk to B, wait, walk to C) and register to the event loop. Think of your objects as state machines.

If you're asking for a language other than ZZT and MegaZeux that has these kind of routines: try Unreal Engine or Scratch.


*Ok, in general. Perhaps your game works on a grid basis and you move to the next cell in one frame.
**This post is a mess. It basically consists of 3 unrelated posts responding to various interpretations of the OP.

commodorejohn
Posts: 1108
Joined: Thu Dec 10, 2009 6:21 pm UTC
Location: Placerville, CA
Contact:

Re: Context-switching/"soft threads" in scripting languages?

Postby commodorejohn » Fri Nov 13, 2015 10:26 pm UTC

Walking is just an example, and yes, there's more complexity to it than that one simple example. (Specifically, the game engine itself should handle allowing multiple objects to run quasi-simultaneously - the problem isn't that part, it's how to allow the object code to pause and resume in the same scope, which just isn't possible in any conventional procedural language I know of.) The problem is that I'm stuck at even the simple example stage, because building elaborate object-as-state-machine constructions is exactly what I've been trying to avoid.

I think I might just have to grit my teeth and roll up my own solution here...
"'Legacy code' often differs from its suggested alternative by actually working and scaling."
- Bjarne Stroustrup
www.commodorejohn.com - in case you were wondering, which you probably weren't.

User avatar
WanderingLinguist
Posts: 237
Joined: Tue May 22, 2012 5:14 pm UTC
Location: Seoul
Contact:

Re: Context-switching/"soft threads" in scripting languages?

Postby WanderingLinguist » Fri Nov 13, 2015 11:49 pm UTC

commodorejohn wrote:standard procedural languages just don't support arbitrarily pausing and resuming execution within a function without bringing the whole program (or at least the current thread) to a halt.


They totally do! Python, C#, and Javascript can all do this. It's used extensively in the Unity game engine to achieve exactly what you're talking about.

What you're looking for are called continuations or generator functions. In almost all languages that support it, this is accomplished via the yield keyword.

Basically, you yield an object that tells your game loop when to wake up your function again. For example, in Python you might have:

Code: Select all

yield game_sleep(15)


Of course, you need to have code in your game loop that does the right thing when it gets a game_sleep object.

commodorejohn
Posts: 1108
Joined: Thu Dec 10, 2009 6:21 pm UTC
Location: Placerville, CA
Contact:

Re: Context-switching/"soft threads" in scripting languages?

Postby commodorejohn » Fri Nov 13, 2015 11:51 pm UTC

Okay, thanks. I'll have to read up on this, but at least I've got a name to put to it :)
"'Legacy code' often differs from its suggested alternative by actually working and scaling."
- Bjarne Stroustrup
www.commodorejohn.com - in case you were wondering, which you probably weren't.

korona
Posts: 495
Joined: Sun Jul 04, 2010 8:40 pm UTC

Re: Context-switching/"soft threads" in scripting languages?

Postby korona » Sat Nov 14, 2015 11:02 am UTC

Note that the naive context-switch / coroutine approach won't scale to a large number of objects as it requires a whole stack (~2 MB virtual memory on Linux) per object. To do this properly in C you would have to build a state machine yourself.

commodorejohn
Posts: 1108
Joined: Thu Dec 10, 2009 6:21 pm UTC
Location: Placerville, CA
Contact:

Re: Context-switching/"soft threads" in scripting languages?

Postby commodorejohn » Sat Nov 14, 2015 6:47 pm UTC

korona wrote:Note that the naive context-switch / coroutine approach won't scale to a large number of objects as it requires a whole stack (~2 MB virtual memory on Linux) per object. To do this properly in C you would have to build a state machine yourself.

That's in Python?
"'Legacy code' often differs from its suggested alternative by actually working and scaling."
- Bjarne Stroustrup
www.commodorejohn.com - in case you were wondering, which you probably weren't.

User avatar
WanderingLinguist
Posts: 237
Joined: Tue May 22, 2012 5:14 pm UTC
Location: Seoul
Contact:

Re: Context-switching/"soft threads" in scripting languages?

Postby WanderingLinguist » Sat Nov 14, 2015 11:57 pm UTC

korona wrote:Note that the naive context-switch / coroutine approach won't scale to a large number of objects as it requires a whole stack (~2 MB virtual memory on Linux) per object. To do this properly in C you would have to build a state machine yourself.


The op isn't talking about C. Continuations/generator functions (i.e. "yield") are used in the Unity game engine in JavaScript, Python, and C# and scale just fine to vast numbers of objects.

lightvector
Posts: 224
Joined: Tue Jun 17, 2008 11:04 pm UTC

Re: Context-switching/"soft threads" in scripting languages?

Postby lightvector » Mon Dec 07, 2015 7:18 am UTC

Another approach if you don't have "yield" in the language but you do have functions as first class. Then if the syntactical properties of your language allow you to omit enough of the delimiters, you can do something like this:

Code: Select all

def doThingA() {
walk b.x, b.y
after 15 {
walk c.x, c.y
after 15 {
do more stuff
after 20 {
do more stuff
}}}}

where "after(n,f)" is function that takes a time to wait x and an argumentless function f and schedules f to be called after n steps. Such as by a method that you mentioned - assigning f to some variable or field that indicates that it should be called again after n steps. In this case, it's not too bad, you just write "after 15 {" in the middle without regard to anything, and then put braces at the end until your editor/IDE is happy that they're all matched.

You could also try lists/arrays of functions as a pseudo way of defining your own mini language without having to go all the way to making a new scripting language, if the set of things you want to do is limited enough.

Code: Select all

my_actions = [
  walkTo c,
  wait 15,
  walkTo d,
  wait 15,
  jump,
  repeat 20 [jump,wait 15],
  function () { code that you don't have a pre-built atomic function-generator for },
  doWhile foo [walkTo e, wait 5],
  ...
]


where, for example, walkTo(point) is a function that takes the point to walk to, and *returns an argumentless function* that only once invoked actually does the walking to that point. And then you add the above list to a queue or something for your object, and then your action loop is like:

Code: Select all

function continueDoingThings() {
  while(!queue.isEmpty()) {
     nextAction = queue.pop();
     if(nextAction is a type/object that is a "wait N")
        return something that indicates that continueDoingThings() should be called again after N seconds
     else if(nextAction is a function) {
        additionalActions = nextAction(); //Perform nextAction
        queue.pushOnToFront(additionalActions); //And take any new actions that resulted from it and do them immediately next
     }
     else ...
   }
}


Where the ability of an action to return more actions lets you do fancy things like "repeat(n,actions)", which returns an argumentless function that when called, returns a copy of "actions" but with "repeat(n-1,actions)" appended to the end. Effectively adding a chain of n copies of "actions" to that part of the queue. Or things like "doWhile(condition,actions)" which expects "condition" to be an argumentless function that returns a boolean and behaves like repeat except that rather than counting down it tests whether "condition()" returns true. You can do some fancy things by passing around functions in this way.

commodorejohn
Posts: 1108
Joined: Thu Dec 10, 2009 6:21 pm UTC
Location: Placerville, CA
Contact:

Re: Context-switching/"soft threads" in scripting languages?

Postby commodorejohn » Mon Dec 07, 2015 8:29 pm UTC

The problem is that the first approach is too complicated to be worth the trouble, and the array-based approach either binds you to static, compile-time definition of object scripts or requires you to develop a mechanism for loading and storing those arrays (i.e. a way to reliably map fixed values to function pointers,) at which point you're halfway into building a virtual machine anyway.

In any case, I looked into continuations for a bit before remembering that I'm really not that fond of Python, and I also discovered that Pygame is significantly lower-level than I'd been led to believe (it looks like it's basically just a Python wrapper for SDL,) so I wound up going back to C and my original plan of just developing a simple bytecode interpreter and application-specific scripting language. Yes, it's a bit more work, but it allows me to do exactly what I want with the language and tools I already know. Thanks to you guys for weighing in, though :)
"'Legacy code' often differs from its suggested alternative by actually working and scaling."
- Bjarne Stroustrup
www.commodorejohn.com - in case you were wondering, which you probably weren't.

korona
Posts: 495
Joined: Sun Jul 04, 2010 8:40 pm UTC

Re: Context-switching/"soft threads" in scripting languages?

Postby korona » Mon Dec 07, 2015 11:22 pm UTC

There is always the option of linking v8 or a Lua interpreter if you want scripting in C.


Return to “Coding”

Who is online

Users browsing this forum: No registered users and 10 guests