0
101 Jun 07, 2013 at 18:47

Hello,
(I think this goes here, really i’m asking about scripting language design, for games, so i really don’t know where to put it).

I was modifying the scripting language (Jewel specifically) I picked for use in my game, and I ran into some trouble when I was attempting to expand on it’s cooperative multithreading. The problem I’m running into is that in most cases a thread is blocking because it’s waiting on an animation to finish, or a sound to play, or something like that, so I should have the scheduler resume the thread on an event. But that can’t work because if another callback says to play a different animation: the one I told to play will never finish, and the thread context will never resume. I could solve this by resuming each thread each tick–most of which would return almost immediately–to check that nothing went wrong; however, while this would work it doesn’t really solve the problem as much as move it, and add a performance hit. The only other thing I can come up with is to have the event manager throw an error if a thread is waiting on something that got interrupted; which would necessitate putting try/catch blocks everywhere in the scripts, which I feel would make them rather rigid and confusing, when flexibility and abstraction are really the whole point of having a scripting language.

Then it occurred to me that the rest of the time I’m almost always waiting on a condition, so I could either create a modified if statement which would block the thread until the condition is true, and a modified while that runs it’s block once every tick instead of constantly; or use yields and loops for similar results. But this has similar problems to the above, only worse, for example if I have an object that uses a while loop to approach another object every tick: I need to check that nothing deleted it each tick. Again I know how to handle this, but I can’t think of any good way to abstract the problem to retain flexibility of a scripting language.

And because the way to deal with this is essentially using a lot of similar code I feel like there must be some way to abstract the issue to make it easy for the script writer, but I can’t think of what that would be, and I’d like to know if anyone here has any ideas on how to do it.

On a related note, if an object has callbacks such as:
method OnCameIntoView(Enemy e);
method OnWentOutOfView(Enemy e);

Obviously I should do something if I am in both callbacks at the same time in reference to the same enemy. I considered doing things like finite state machines and a “schedule(functor, time)” function (mostly because i saw this in another game engine) instead of yielding, but I don’t see how those would do more than move the problem to another structure without making it easier to solve. Maybe i’m just thinking about it in the wrong way, but this is something i can’t think of how to solve, and any advice would be appreciated.

Thanks!

3 Replies

0
165 Jun 07, 2013 at 23:21

@GeatMasta

if another callback says to play a different animation: the one I told to play will never finish, and the thread context will never resume.

I would think that being interrupted counts as finishing. If a thread is waiting on an animation, it should resume if the animation is terminated for any reason. It could optionally get a return value from the wait that tells you whether it ran to completion or was interrupted.

Consider, for instance, a script that turns on a particle system when the animation plays, intending to turn it off when the animation finishes. If the animation is interrupted but the script doesn’t get notified, it leaves the particle system turned on forever.
@GeatMasta

if I have an object that uses a while loop to approach another object every tick: I need to check that nothing deleted it each tick

It actually doesn’t seem that unreasonable to me that scripts that want to hold object references across multiple frames should be required to check that their object hasn’t been deleted each time they resume. For some scripts it would be ok to just put a try-catch block around the whole thing, but other scripts would want to trigger specific behavior if an object goes away, e.g. a turret looking for new targets if its current target dies.

If you have a static type system in the language, you might be able to get it to help enforce the rule. I’d probably use weak references for objects in script, where the reference gets automatically nulled out if the object goes away. If you have nullable and non-nullable references, you could make a rule that a non-nullable reference can’t remain in scope across an async function call (e.g. a wait). Only the nullable references can, and they force you to do a null check before looking at the object.
@GeatMasta

Obviously I should do something if I am in both callbacks at the same time in reference to the same enemy.

I don’t understand this part. Can you explain further? How would you be “in both callbacks at the same time”?

0
101 Jun 08, 2013 at 14:53

@Reedbeta

I don’t understand this part. Can you explain further? How would you be “in both callbacks at the same time”?

say I have something like :

method moffet::[color=#282828][font=helvetica, arial, sans-serif]OnCameIntoView[/font][/color](Enemy e) {
if(e.strength > 1.5*strength) {
FindPath(AlarmStation::all(), NEAREST | SET_PATH );
//I find this sort of hard to read, which is how the whole thing got started.
if(-1 == BlockOn(FollowPath()) ) {
//interrupted for some reason
return;
}
//etc...
}
else {
if(-1 == BlockOn(FindPath(e, FOLLOW_PATH) {
return;
}
//etc...
}
}


OnWentOutOfView could be called when the moffet is approaching the alarm station because it’s walking away from the enemy, or when approaching the enemy because the enemy ran off.

I hadn’t really thought of modifying it to set dangling weak references to null; but I suppose I could have an array in the moffet of strong references to visible objects, and give the callback with a weak reference to the strong reference; so when it no longer sees it the reference gets nulled… But in the past pointer pointers have usually caused me trouble, does that sound like a good design decision?
@Reedbeta

It actually doesn’t seem that unreasonable to me that scripts that want to hold object references across multiple frames should be required to check that their object hasn’t been deleted each time they resume.

The problem I have with this, and I don’t know if this is true or not as I haven’t done this before, but I think that the bytecode execution will probably be where the majority of processing is done; if I can come up with a decent way to push this check to C++, then doing these checks will be about 10x faster. In an individual object this won’t matter much, but I worry that because almost everything will be resuming something each tick it will lag if these checks are done in script.

0
175 Jun 08, 2013 at 15:00

I would not personally put any requirement in scripts that state you must deal with synchronization. When you provide a high level API, it should be designed to solve a problem easily and correctly. Micro-managing these issues will only bloat your scripts and will likely reduce their maintainability. Reed’s suggestion is onto a good start; however I also believe that your threading design may need to be looked at as well. Scripts should be notified when things happen and should not actively wait on things. It’s analogous to waiting in line at the store vs ordering online. The later allows you to do other things in your spare time while you wait for the delivery. A script will want to register events with a particular object, like when it’s destroyed. This would be acceptable functionality to add into your scripts and would be much more preferable than a reactive approach where you suddenly find out the object no longer exists in your runtime loop.

Finite state machines are one way to control the flow of logic, but I’ve grown to prefer behavioural patterns instead. They’re less restrictive and allow code reuse. The way this works is that you attach a set of behaviours to an entity (ie: behaviours can be scripts!). Behaviours will react to a specific state, such as an animation change, a change in the environment, a change in other entity states, etc. These behaviours in turn execute their logic. For example, I may have a ReactWhenHitBehaviour class that listens for when an entity is hit. This behaviour will schedule for an animation and sound to be played. Each behaviour is responsible for their own domain, so I never have to check up on them. Behaviours are also thread safe because when the object no longer exists, neither do the behaviours. Likewise, when other objects are destroyed their very design is to be notified of these actions so they can update themselves. In your final question where an enemy comes into view, you would activate the aggressive behaviour. When the enemy leaves view, you would restore the passive behaviour. Optionally you could add other behaviours like confusion if the enemy suddenly vanished, the AI would try to look around before restoring his passive behaviour. Essentially, each behaviour would have its own AI for controlling the entity and you can easily cherry pick which ones you want to use.