How do I avoid race conditions in the Hammer I/O system?

Discussion in 'Mapping Questions & Discussion' started by Sappykun, Jan 10, 2020.

  1. Sappykun

    Sappykun L1: Registered

    Messages:
    10
    Positive Ratings:
    0
    TL;DR What are good patterns for preventing race conditions when using sequential logic?

    ----

    I have a KOTH map with a lot of logic point entities. The operations these entities perform must be completed in a certain order, based on which team caps the point and when.
    Previous versions of this map have created race conditions regarding the map's logic that I am unsure how to prevent in the future. Currently I have "fixed" these race conditions using delays, but all that really does is make the race conditions less likely to happen. I would like to know if there is a way to ensure operations are executed in a specific order using Hammer's I/O system.

    For example, I need to set a model's animation BEFORE I change the playback rate on said animation. If I set the playback rate first, changing to a new animation will automatically reset the playback rate to 1.
    I can fire the SetPlaybackRate output with a delay of 0.01s after SetAnimation, but what if SetAnimation ends up taking longer than 0.01s? I can set the delay higher, but what if SetAnimation ends up taking longer than to whatever value I set the delay?

    The only potential solution I can think of is to use two relays. The first relay calls SetAnimation and triggers the second relay. The second relay calls SetPlaybackRate. In this situation, is it guaranteed that SetAnimation occurs before SetPlaybackRate, assuming no command is delayed?

    Are there any real solutions to prevent these types of race conditions, or are delays the only way to deal with them?
     
  2. Crowbar

    aa Crowbar Live demonstration of the hairy ball theorem

    Messages:
    1,458
    Positive Ratings:
    1,270
    Is this possible? I'm not sure it is. I'd actually wager that all I/O happens in the same thread.
     
  3. Da Spud Lord

    aa Da Spud Lord Occasionally I make maps

    Messages:
    1,155
    Positive Ratings:
    791
    I don't think the scenario you're describing is one that you need to worry about. TF2 splits the game time up into different "ticks", which represent roughly equally spaced units of in-game time. Every action that could be done in a single game tick is completed in that tick, and all ticks are done on the same thread, in proper order. I see no reason why calling SetAnimation for a model would take longer than a single game tick. A delay of 0.01 should be sufficient to push the SetPlaybackRate input to the next game tick.

    I'm currently working on a map with large logic chains wherein a lot of things need to happen, in a specified order, and they need to happen really fast. The entire system depends heavily on delays of 0.01 seconds. I've encountered no race condition issues or otherwise had issues with things occurring out of order. For as annoying and unreliable as the Source engine is sometimes, this is one thing that I've never had to ever worry about.

    What is it that you're trying to achieve that is causing race conditions?
     
    • Agree Agree x 1
  4. Penguin

    aa Penguin Clinically Diagnosed with Small Mapper's Syndrome

    Messages:
    2,017
    Positive Ratings:
    1,435
    as others have said, it is not possible to get a race condition if you use any amount of delay. all I/O in Source is single-threaded.
     
  5. Sappykun

    Sappykun L1: Registered

    Messages:
    10
    Positive Ratings:
    0
    Thanks for the replies. Interesting to hear that the IO system is actually single-threaded; I was getting non-deterministic results when I either didn't set the delay or set it too short.

    The map I am working on has a stage with about a dozen models, split up by RED and BLU teams. When the round starts, two random songs are selected, and each team's model set gets a specific animation set corresponding to the song. The models for each team only appear when the corresponding team caps, and the song and animation start playing. When they lose the point, the models disappear, and the song/animations "pause" (by setting the playback rate on both to 1/1000 the normal speed so they can be resumed later).

    I found that on occasion, when setting the animation and the playback rate, the animation gets set before the playback rate is applied, meaning the animation starts playing when the song is paused and becomes desynced with the song. I wasn't sure what was causing this, and my only conclusion was that there was a race condition somewhere (since Source I/O has no concept of specifying operation order beyond delays) due to some processes taking more than one tick for whatever reason.
    I've since redone the logic for selecting the music and animations, so race conditions in this situation shouldn't be an issue anymore.

    --------

    This is kind of an abstract/confusing/demanding topic, so I apologize if I'm not clear or I'm asking too much. Let's say I have the following setup with some generic entities:

    BigEntity:
    - Call Trigger on EntityChain1 after 0.00s
    - Call Trigger on EntityChain2 after 0.01s

    EntityChain1:
    - A bunch of calls to various things (ends with EndChain1)

    EndChain1:
    - Call SetValue "a" on SmallEntity after 0.03s

    EntityChain2:
    - A bunch of calls to various things (ends with EndChain2)

    EndChain2:
    - Call SetValue "b" on SmallEntity after 0.01s

    Assume the EntityChain calls are completely arbitrary and affect no other entities beyond the final call in EndChain.
    How would I follow the order of operations in this situation?

    What is the final value of SmallEntity if:
    - EntityChain1 and EntityChain2 take the same time to complete
    - EntityChain1 takes longer than EntityChain2
    - EntityChain2 takes exactly 0.01s longer than EntityChain2
    - EntityChain2 takes much longer than EntityChain2 (more than 0.01s)
    - The delay in the first call to EntityChain2 is set to 0.00s
    - All delays outside of the EntityChain entities are set to 0.00s
    etc.
     
  6. Da Spud Lord

    aa Da Spud Lord Occasionally I make maps

    Messages:
    1,155
    Positive Ratings:
    791
    [​IMG]
    Let's find out.

    I created a simple test map (attached below, for your own experimentation) to test all of this. It's set up pretty much exactly as you described:
    upload_2020-1-15_14-58-26.png
    SmallEntity is a logic_branch, which is suitable since we only have two possible values for it. The two entities below it are game_text, which display its value every time that changes. Everything else is a logic_relay.

    I ran each test 10 times for a decent sample size. The tests are the same as the ones you listed, in the same order. Some tests are repeated with variables changed. Unless otherwise noted, each EntityChain takes 1 second. Here's a table of my results, listing how many times I got each value as a result for each test:
    Test Description Value A Value B Notes
    "EntityChain1 and EntityChain2 take the same time to complete" 10 0
    "EntityChain1 takes longer than EntityChain2" 10 0 EntityChain1 takes 1.10 seconds.
    "EntityChain1 takes longer than EntityChain2" 10 0 EntityChain1 takes 1.01 seconds.
    "EntityChain2 takes exactly 0.01s longer than EntityChain2" 4 6 I presumed you meant EntityChain2 takes longer than EntityChain1. EntityChain2 takes 1.01 seconds. Results were random and unpredictable.
    "EntityChain2 takes much longer than EntityChain2" 0 10 I presumed you meant EntityChain2 takes longer than EntityChain1. EntityChain2 takes 1.10 seconds.
    "EntityChain2 takes much longer than EntityChain2" 0 10 I presumed you meant EntityChain2 takes longer than EntityChain1. EntityChain2 takes 1.02 seconds.
    "The delay in the first call to EntityChain2 is set to 0.00s" 10 0
    "All delays outside of the EntityChain entities are set to 0.00s" 10 0 I expected the results to be random and unpredictable, although this clearly wasn't the case.
    "All delays outside of the EntityChain entities are set to 0.00s" 10 0 Second test to make sure everything was set up properly.
     

    Attached Files:

    • Thanks Thanks x 1
  7. Sappykun

    Sappykun L1: Registered

    Messages:
    10
    Positive Ratings:
    0
    Case 4 seems to confirm my suspicions, at least when two logic branches try to affect the same entity.
    Thanks for the in-depth tests! I appreciate it.