HXCPP Multithread hangs on forever while loop.



  • Hi There!

    I have some enemy AI code for my game that runs in a separate thread. I've put it in its own thread because else the game cannot keep up. The actual code that i'm using is kind of similar to this test code.

    // Source: Runner.hx
    package;
    
    #if cpp
    import cpp.vm.Thread;
    #end
    #if neko
    import neko.vm.Thread;
    #end
    
    class Runner {
        #if (cpp || neko)
        private static var _thread:Thread;
        #end
        private static var list:Array<Int>;
    
        private static var keeprunning:Bool;
    
        public function new() {
            keeprunning = true;
            list = new Array<Int>();
            list.push(1);
            runme();
        }
    
        public static function runme() {
            #if (cpp || neko)
            _thread = Thread.create(runthread);
            #end
        }
    
        private static function runthread()  {
            while(keeprunning) {
                for (e in list) {
                    trace('# Running: ' + e);
                    /* ... do stuff ... */
                }
            }
        }
    }
    

    Now, this code as is will run on both CPP and Neko. But if you comment out the trace statement

    // trace('# Running: ' + e);
    

    or comment out the push

    // list.push(1);
    

    or remove the body of the while loop.

    private static function runthread()  {
        while(keeprunning) {
            /* ... no body ... */
        }
    }
    

    then the game will hang on CPP (Windows). Neko is unaffected and runs as desired!

    I'm thinking this has to do with optimizations on the compiler, but am very unsure. Perhaps the while forever loop is just not the way to go.? Or is there perhaps an other solution?

    Many thanks for any tips you might have!

    Best regards!



  • Good News Everyone!

    I was pretty stuck on this but am able to get good working CPP builds of the above code using -Dnext. Perhaps when not providing this switch then legacy and new codebases overlap somehow? :S

    I'm using HaxeFlixel on VSCode with the below versions.

    HaxeFlixel command-line tools (1.2.1)
    installed Flixel version 4.2.1
    OpenFL Command-Line Tools (3.6.1-LQBZc4)
    Haxelib library manager 3.3.0
    Haxe 3.4.2 (git build master @ 890f8c7)
    

    I will do some extended test on my own code later this day on this subject. But because the test code is working now I have full confidence in it.



  • Regretfully I celebrated too soon! ;(. The -Dnext switch only postpone the freezing-up but in both my local code and the above test code. It will still eventually freeze. This is really driving me crazy!

    In my localized code the problems start when a list of enemies (FlxSprites) has a length of one and that last/only enemy is chasing me with some AI code from the extra thread. I just might be able to solve this by pushing one or two extra enemies in the list which I will hide somewhere and ignore in the actual game.

    Either that or I'm doing something wrong, or drop the enemy AI in total - which I would really not enjoy doing.

    Does anyone have any insight on this? I belief a while-forever loop on a second thread is pretty standard practice, and it works great in Neko. But still I'm new to Haxe and there just might be a better/other way..!?



  • I don't have a lot of experience with threading, especially in Haxe. It could be that the compiler is removing the body of your while loop because there's nothing to do inside.

    Reading your post again, what exactly are you trying to achieve? You said:

    [...] Now, this code as is will run on both CPP and Neko.

    Isn't it working the way you want?

    Maybe you need the Haxe equivalent of Thread.Sleep, if such a thing exists?



  • @ashes999 Thanks for your reply.

    This is just test code. With commenting out the trace or push I get close to whats happening in my actual gamecode. The actual gamecode is big and has load of localized stuff in it which probably is just going to raise more questions then answers at this point. Also it still under heavy development so its a bit messy here and there. Just like the test code my real code is working on Neko, but if an Array that I'm using gets to one enemy then it freezes. (Just like if you don't push the integer or comment out the trace in the test code). This behavior is irrational but reproducible - hence the test code - and needs to be fixed. If I can fix it in the test code I can apply it in the real code ;)

    I'm working on an other way of doing this instead of using the while-forever loop. I want to use update() to call rumme() and check if the thread is running or not. If not, then execute the thread. This already shows lots of promise on the test code. But i need to work it out a bit. I will get back on this shortly.

    Thnx!



  • @ashes999 , your idea of providing a sleep/timer was actually quite helpful. Especially for the test code. I could not find one on the Thread itself, but documentation is a bit low on this subject in Haxe. But introducing a regualar FlxTimer on the thread will fix the problem.

    // Source: Runner.hx
    package;
    
    #if cpp
    import cpp.vm.Thread;
    #end
    #if neko
    import neko.vm.Thread;
    #end
    
    import flixel.util.FlxTimer;
    
    class Runner {
        #if (cpp || neko)
        private static var _thread:Thread;
        #end
    
        private static var list:Array<Int>;
        private static inline var kSleepTime:Float  = 0.2;
    
        public function new() {
            list = new Array<Int>();
            list.push(1);
            runme();
        }
    
        public static function runme() {
            #if (cpp || neko)
            _thread = Thread.create(runthread);
            #end
        }
    
        private static function cbTimer(t:FlxTimer) {
             runthread();
        }
    
        private static function runthread()  {
            for (e in list) {
                trace('# Running: ' + e);
                /* ... do stuff ... */
            }
            new FlxTimer().start(kSleepTime, cbTimer, 1);
        }
    }
    

    You can do anything you want with in now. Comment out the push or the trace is not a problem anymore. You can set kSleepTime to anything you want including 0. It is still necessary to use it because else you might be going to recurs too deep if you keep calling runthread internally, which can always lead to problems with environments. I don't know if Haxe can take an endless recursion. - It would surprise me if it can, there is usually a limit on it.

    I do have a slight hunch that the while loop is not compiled to a actual while loop. Its probably some function or macro which is not build for this functionality. The problem is definitely the while loop though! Getting rid of it in any way will solve the problem.

    As said I did play around with calling runme from the gamestates update(). This works but the performance was very bad. I can only recommend doing it like that if you occasionally need a thread on demand. Or have the thread usually runs a while before ending. (way longer then couple of updates()).

    Anyways I'm very happy with this!

    \(0^O)/



  • Glad you figured it out. Sorry I'm not very useful with threading.



  • I already found oud yesterday that the FlxTimer will not solve this. Just thought I would sleep over it before reacting. As soon as the function called by Thread.create(); ends, then the thread ends. Which is kind of normal behavior and the reason for using an endless loop to keep it going. The callback on the FlxTimer does not run in its own thread. Its just the main thread.

    Now, adding a sys.sleep(0.1) to the while loop will make it more stable. But I still found some minor issues with it when clicking on and off the game. There are cases when you do that alot then the thread dies. Which does not make me confident enough to use it!

    Some other critic I have on the threads is debugging them. Normally when you have an access violation (accessing a null pointer) the game crashes and you get a very decent execution trace which will give you a good idea where it all went wrong. Threads do not do this. They just complain and die on you. You have to figure out for yourself where it all goes wrong. If you have a big piece of code, this can be very irritating. The main thread of the game also just keeps running. So, if you don't keep an eye on the log, you wont even know its dead until you see some weird behavior in your game.

    I'm going to solve this by getting rid of the heavy-duty parts of the code and keep the AI less smart. I've tested this yesterday and this will work, even on my slowest test device Samsung Galaxy s2. This is more a pragmatic solution. I would still enjoy a technical one because I might need to thread later in this or any other project.

    For now i'm pretty done with it though! Its frustrating me too much and taking up too much time.


Log in to reply