Skip to main content

News

Topic: Issue with updatescript (Read 2892 times) previous topic - next topic

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • miniSphere Developer
Issue with updatescript
So I just found out the hard way that, if a call to the updatescript is in progress, it won't be called again.  Normally this isn't an issue, but in my particular case it's a big problem.

You see, in the Specs engine, I've implemented a custom threader to allow simultaneous operations (e.g. asynchronous tweens, or have a separate thread for battle menus while the battle engine runs in the background, that kind of thing).  If you need to wait for a certain thread to complete without affecting other asynchronous stuff, you call Threads.wait(); which enters an update/render loop until the specified thread terminates.  Here's the problem, though: MapEngine() runs its own game loop.  Which means, in order for the threader to still work on the field, I have to add calls to Threads.updateAll and Threads.renderAll to the update and render scripts respectively.  This is fine and works 95% of the time--right up until somebody calls Threads.wait().  Since that almost always happens in a thread's update function, this blocks the updatescript itself while the threader runs its wait loop--which itself includes a call to UpdateMapEngine()! This in turn causes the entire threader to deadlock.

There's a couple workarounds I've considered already. One was to forego the update/renderscripts entirely and just add a thread that updates the map engine itself. While this would keep the map engine running during battles, menus, etc, the threader would be unavailable on the field.  This is the cleanest method, but the lack of threading on the field will likely be a deal breaker.

Another method is to call UpdateMapEngine AND Threads.updateAll in the wait loop.  Unfortunately, this causes double updates when the updatescript doesn't happen to be blocked--for example, when Threads.wait is called from a persist.js map script.

Then you have the last method, the one I'm using now, which doesn't call UpdateMapEngine at all, only Threads.updateAll.  The problem here should be obvious: The map engine is frozen in place until control returns to the original MapEngine game loop (e.g. when the battle is over).

None of these solutions is really ideal for what I'm going for with Spectacles, so I'm going to ask, is there a better method to solve this problem than the three fixes above?
  • Last Edit: June 27, 2013, 06:21:22 pm by N E O
miniSphere 5.2.11 - Cell compiler - SSj debugger - thread | on GitHub
For the sake of our continued health I very much hope that Fat Cerberus does not become skilled enough at whatever arcane art it would require to cause computers to spawn enourmous man eating pigs ~Rhuan

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Issue with updatescript
Reply #1
Can you show a code sample of what you just wrote? I use a modified version of your scenario (so that it uses the map engine) and I don't get deadlocks when waiting... Hmm.
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • miniSphere Developer
Re: Issue with updatescript
Reply #2
I'm at work right now, I'll post the code later.  The issue is when somebody does a wait call INSIDE the updatescript (which the way Specs is designed, is almost always the case), which means subsequent calls to UpdateMapEngine in a wait loop don't call the updatescript (it's already in use) and boom--deadlock.
miniSphere 5.2.11 - Cell compiler - SSj debugger - thread | on GitHub
For the sake of our continued health I very much hope that Fat Cerberus does not become skilled enough at whatever arcane art it would require to cause computers to spawn enourmous man eating pigs ~Rhuan

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • miniSphere Developer
Re: Issue with updatescript
Reply #3
Okay, so here's the wait code for the Specs threader:

Code: (javascript) [Select]
SetUpdateScript('Threads.updateAll();');
SetRenderScript('Threads.renderAll();');

Threads.waitFor = function(threadID)
{
    while (Threads.isRunning(threadID)) {
        RenderMap();
        FlipScreen();
        UpdateMapEngine();
    }
};


Now suppose I start a battle, here's some code to illustrate the issue (essentially pseudocode, my actual battle engine is more complex than this):
Code: (javascript) [Select]
Battle.prototype.go = function()
{
    // Prepare state variables in preparation for fight...
    this.$mainThread = Threads.createEntityThread(this);  // An "entity" in the Specs engine is any object with an .update method.
    Threads.waitFor(this.$mainThread);
};

// ...

Battle.prototype.update = function()
{
    // ...
    if (turnIsUp) {
        var menuThread = new MoveMenu(actingUnit);
        Threads.waitFor(menuThread);
    }
};


All's well and good, the battle starts running, but the minute the menu is hit everything freezes up.  See, the battle engine is already running inside of a waitFor() loop itself, which means Battle.update()--and therefore waitFor(menuThread) is being called as part of the update script.  This means the updatescript enters a wait loop, waiting until menuThread terminates.  However, menuThread never terminates because the updatescript is caught in a loop and Sphere won't run another instance, so no threads get updated ever again.  And so, the whole thing deadlocks.
  • Last Edit: May 12, 2013, 09:27:57 pm by Lord English
miniSphere 5.2.11 - Cell compiler - SSj debugger - thread | on GitHub
For the sake of our continued health I very much hope that Fat Cerberus does not become skilled enough at whatever arcane art it would require to cause computers to spawn enourmous man eating pigs ~Rhuan

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Issue with updatescript
Reply #4
I think part of the problem is that you need to think of doing the inverse when waits occur. The update and render scripts must be free to update at any time, there should be no forcing them. When you call a wait, rather than letting the background idle until the job is done, stop all other things - making it seem as if we are waiting - until the job is done.

I don't run into this problem since I use a state machine for my battle system, that works on a stack. The top of the stack can update, render, and have input. The rest can only render. Now in your case you may not want that, you want background processes to update as well. So, I would indicate what is the top thread, and have the wait really mean: "are we a top thread?". Only top threads can receive input.

Let me guess, the main thread observes the battle variables: who's in charge? who's next? do we show a menu? (etc.) Then when the menu is shown, have it be the top thread (but I can see you still want the background to update). Or why are you waiting? Because if waiting really means: do nothing but update/render the menu until the menu closes, you would do better to just use a state manager + stack rather than a threaded environment. Either way your threaded environment will end up emulating that anyways.

I wish Sphere had a basic threading API.
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • miniSphere Developer
Re: Issue with updatescript
Reply #5
I hate state machines, any time I've implemented one of those I've ended up with spaghetti code galore (compared to the Specs engine, which is overall very clean code).  The stack of states with only one on "top" tends to be limiting in a lot of cases, as well.  For instance, how do you back out of a long state chain immediately--say you're several submenus deep and want to go directly back to the map screen--without having special-casing at each level to handle that? There's no feasible way to generalize that. You could have the sub-sub-submenu's cancel handler pop 4 states off the stack simulatenously, for instance, but if you later decide to reorganize the menus and miss changing some of the multi-pops you end up with really weird bugs like canceling a battle menu sending you back to the field instead of returning to the fight.  And working around these limitations tends to turn the code into an unmaintainable mess before long.   At least, that's been my experience.

Anyway, the reason I'm doing threading is because having asynchronous operations is highly desirable.  75% of the time, nobody's going to be waiting on anything, but when it does happen, I don't want everything else to grind to a halt either.  Tweening operations, battler idle animations while the player chooses a move, animated gauges, damage numbers over a sprite... none of these things will ever enter a spinlock, and the alternative to threading is to have all this stuff done by the main update function (would be necessary with a state machine with only one "top" state), which would make Battle.update() extremely bloated.  The threaded code is much, much cleaner!

And I'm quite familiar with the concept of only having one thread get input--Scenario does that.  I didn't bother implementing Z-order in the Specs threader though, because I've never had a case where two threads that both accepted input were running simultaneously.

Ultimately though, none of this would be an issue at all if Sphere was willing to start more than one instance of the updatescript.  There's no good reason I can see to prevent recursive invocation, it's essentially just a weirdly-implemented callback function... but it is what it is, I'm just looking for a workaround that doesn't require me to refactor my entire battle engine.  That's not really feasible at this stage, unfortunately.
  • Last Edit: May 12, 2013, 10:28:15 pm by Lord English
miniSphere 5.2.11 - Cell compiler - SSj debugger - thread | on GitHub
For the sake of our continued health I very much hope that Fat Cerberus does not become skilled enough at whatever arcane art it would require to cause computers to spawn enourmous man eating pigs ~Rhuan

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Issue with updatescript
Reply #6

Ultimately though, none of this would be an issue at all if Sphere was willing to start more than one instance of the updatescript.  There's no good reason I can see to prevent recursive invocation, it's essentially just a weirdly-implemented callback function... but it is what it is, I'm just looking for a workaround that doesn't require me to refactor my entire battle engine.  That's not really feasible at this stage, unfortunately.


Well there is other stuff going on behind the scenes such as framerate handling and other such things. If the update script was able to call itself forever then it would just loop forever without handling the other things that must be handled by the map engine. I'm sure if there was a manual mode, then you could do that other legwork in another thread of yours. Unfortunately you just don't have access to that hidden bookkeeping.

Did you try looking into creating mutex locks?
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • miniSphere Developer
Re: Issue with updatescript
Reply #7
As far as bookkeeping, UpdateMapEngine() and RenderMap() work just fine in a spinlock, there's no reason it shouldn't call the updatescript as part of that just because another one is already in progress.  If it recurses too deeply the JavaScript engine is going to error out anyway.

In any case, I found a workaround.  Admittedly it's a hack, but it does what I need:
Code: (javascript) [Select]
this.waitFor = function(threadID)
{
    while (this.isRunning(threadID)) {
        RenderMap();
        FlipScreen();
        this.$hasUpdated = false;
        UpdateMapEngine();
        if (!this.$hasUpdated) {
            this.updateAll();
        }
    }
};
  • Last Edit: May 12, 2013, 11:15:09 pm by Lord English
miniSphere 5.2.11 - Cell compiler - SSj debugger - thread | on GitHub
For the sake of our continued health I very much hope that Fat Cerberus does not become skilled enough at whatever arcane art it would require to cause computers to spawn enourmous man eating pigs ~Rhuan

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • miniSphere Developer
Re: [Scenario] Issue with updatescript
Reply #8
@NEO
This wasn't a Scenario issue, but an issue with the way Sphere handles nested calls to the updatescript. So not so sure this was the right place to move it...
miniSphere 5.2.11 - Cell compiler - SSj debugger - thread | on GitHub
For the sake of our continued health I very much hope that Fat Cerberus does not become skilled enough at whatever arcane art it would require to cause computers to spawn enourmous man eating pigs ~Rhuan

  • N E O
  • [*][*][*][*][*]
  • Administrator
  • Senior Administrator
Re: Issue with updatescript
Reply #9
Ok, it's generalized into "Script Support" now.
  • Last Edit: June 27, 2013, 08:26:37 pm by N E O