Race loops suddenly stop running, function becomes unresponsive

I am very new to this kind of programming, and I’ve written a bit of a convoluted game that has started producing a bug that I cannot seem to resolve. I have rewritten this code many times in different ways, and I had the same issue without using a race expression. This latest iteration is just another attempt at avoiding this bug.

To keep my game organized, I have several verse creative devices that function as “managers” for different game mechanics. I have a Player Manager and an Enemy Manager. I have a function in my Player Manager called Stun, which I call from my Enemy Manager when the enemy damages the player. The Stun function needed to be publicly available, so it is in a public module sitting alongside my creative device module within the same verse file. The creative device module sets up editables for my VFX prop assets and assigns them to a weak_map session variable. When I call the Stun function, it starts by referencing some weak_map session variables: PlayerStunned = weak_map(session, [fort_character]logic), ResetPlayerStun = weak_map(session, [fort_character]logic) and VFXProp = weak_map(session, creative_prop_asset). Throughout the function, I pull references to these variables several times and sometimes set new values to them when needed.

Here is a simplified version of my function:

Stun(PlayerCharacter)
    if PlayerStunned is true
        Set ResetPlayerStun to true
        Exit function
    else
        Set PlayerStunned to true
        Initialize TimeRemaining to 3.0
        Spawn StunVFX at the PlayerCharacter location
        Put PlayerCharacter in stasis
        loop
            Sleep(0.2)
            race
                block
                    loop
                        if TimeRemaining < 0.2
                            Set StunCompleted to true
                            Break loop
                        else
                            if PlayerCharacter moved
                                Move VFX to new PlayerCharacter location
                            Decrement TimeRemaining
                            Sleep(0.2)
                block
                    loop
                        if ResetPlayerStun is true
                            Reset ResetPlayerStun
                            Increment the TimeRemaining by 2.8
                            Break loop
                        else
                            Sleep(0.2)
            if StunCompleted
                Release PlayerCharacter from stasis
                Dispose of VFX
                Set PlayerStunned to false
                Break loop

The goal of the function was to allow my enemy to stun the player and to give them the ability to “stun lock” the player if they are not careful by adding more time to the stun duration with each additional Stun call.

The function runs perfectly if I call a stun once or just a few times. However, if I call enough stuns for ~30 seconds of run time, the function hangs with just a few seconds remaining. I have added debug prints all along my code, and the last things that print are the “end of block # loop” checks alongside the Sleep() at the end of each race block loop. The loops also have a “start of block # loop” print that never fires after this point. The race never finishes, so the rest of the code does not run, and the player remains in stasis.

I have spent too long on this one issue, but I am at a loss for what could be going wrong. I wonder if there are some limitations of race or loops that I am unaware of or if I am hitting some threshold from running too many loops at once. My current assumption is that there is something wrong with how I am interfacing with the session variables or that I’ve misunderstood how I should structure this function.

I would greatly appreciate some external insight into this!

Hi.
I don’t necessarily have an answer to this, but I seem to remember there being issues with async functions inside of loops. I am curious if there is a way to do this without the race being inside a loop itself. For instance, doing a recursive call once one or the other block wins versus it being in a loop. Just my initial thoughts.

After testing a few more scenarios, I finally understood my mistake. However, I do not know how to fix it.

I have called this function from within the custom NPC behaviour I wrote for my enemy.

When testing this in my game, I would allow my enemy to hit my player character and then eliminate the enemy. After eliminating an enemy, the entity does not disappear immediately. After elimination, the enemy entity takes about 10 seconds to be entirely removed from my game. So, if I queued enough stun time before eliminating the enemy, the enemy would be removed before the stun loop could complete. When the enemy is removed, along with its behaviour instance, the spawn{status_manager.Stun(PlayerCharacter)} call I made is also destroyed, stopping the function.

A simplified version of my current structure is like this:

player_manager.verse

player_manager<public>:= module: 

    LoopFunction<public>()<suspends>:void=

        loop:
            Print("loop running")
            Sleep(5.0)

custom_npc_behaviour.verse

custom_npc_behavior := class(npc_behavior):

    OnBegin<override>()<suspends>:void =

        spawn{player_manager.LoopFunction()}

Is there a way I can call this function so it is disconnected from the NPC initiating it?