Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ViZDoom for behavioral modelling; stabilizing time-diff between states #582

Open
sean-mulready opened this issue Jan 9, 2024 · 4 comments
Labels

Comments

@sean-mulready
Copy link

Hi!

Quick introduction: My name is Sean, I'm a psychology undergrad in Magdeburg, Germany. I'm trying to use ViZDoom for an experiment in the field of behavioral modelling. So, yes, I'm not really using it as it is supposed to for computed RL but with people playing it (Spectator-Mode).
I'll try to make it as short as possible without skipping crutial details

Used System: WSL2 via VSCode on Windows 11
ViZDoom Version: 1.2.3
Python-Version: 3.10

You can see the full code here: https://github.com/sean-mulready/Vizdoom-Scripts [it's the "new_test_just_basic.py"]

When I'm looking at the data (via my datause.r-script in the same rep), I see first of all that the time difference between two states isn't stable. I'm already working with arrays to keep the loop within an episode as fast as possible.
The mean and median of the difference in time between to recorded states is 0.028 so about 30ms.

Questions:

  • is there a way to stabilize the time difference?
  • is there a way to get down to a time difference of ~10 ms ?

I already tried my script on a linux-only machine but same results.

I appreciate any help in any way!

@Miffyli
Copy link
Collaborator

Miffyli commented Jan 10, 2024

I see you are measuring the time difference with Python's time.time. I reckon this will result in bit of variation between steps, as the exact time it takes for vizdoom to simulate a step + handle all Python stuff. It may not exactly reflect how the timing is happening inside the game.

To reduce this variation, I would recommend making the busy loop that interacts with environment and stores information as quick as possible. However it already looks like it is pretty fast.

Another thing for more accurate time measurements, you could use time.perf_counter instead of time.time, which providers more reliable time measurement (but does not help with this case).

Edit: To follow the game (Doom time), use the Tic array instead of Time.

@mwydmuch
Copy link
Collaborator

Hi @sean-mulready, thank you for opening the issue. I believe you wrote an email to me some time ago. I apologize for the lack of response from my side... unfortunately, I lacked time to respond to it, as there was quite a lot of stuff to unpack. I'm sorry :(

When it comes to this question, I believe the @Miffyli is right here. There are a few reasons, that I see and can cause the spikes in the performance, but I don't want to get into that here. While for sure it is possible to make your script a bit faster, I would like to propose to you some other solution that will be much better for your studies in my opinion.

If I understand correctly, you would like to analyze the games of human players, and your scripts log some data while they play. We actually have the functionality in ViZDoom that allows you to "record" the games into very small files that can be used to recreate the games perfectly with completely different settings (different resolutions, additional game variables, etc.), the "recording" files store the sequence of actions that the player took during their game and, based on that, the library is able to recreate what happened. It works both for PLAYER as well as SPECTATOR modes. The usage of this functionality is demonstrated here: https://github.com/Farama-Foundation/ViZDoom/blob/master/examples/python/record_episodes.py

This way, you can create one script that just runs and records the game without any processing (resulting in no problem with performance), and then you can analyze it in another script without worrying about real-time performance. This will also allow you to improve your analysis and repeat it without the need to get people to play the game again. This functionality is battle-tested, as it was used to create all the videos on our YT channel: https://www.youtube.com/@ViZDoom/videos -> we first recorded the games (some played by algorithms, some by people) and created the videos later.

If you would like to ask some more questions, I invite you to our Farama discord server, where you can find me almost every day, I'm definitely much faster to respond to short chat messages: https://discord.com/invite/PfR7a79FpQ

@sean-mulready
Copy link
Author

Hi @Miffyli , hi @mwydmuch ,
thank you both so much for responding so fast (and no worries about any unanswered mails :) ). I'll keep this short:
The suggestion with the two scripts for recording and analyzing got me so excited when I read about last night, that I already spent 3 h today to copy/paste and write these scripts out of the current. And I'm excited as the mean (and median) of my time-diff already got down to 23 ms! And the whole data looks more stable now.
Next thing is to implement the suggested time.perf_counter just out of curiosity :)
I already encountered some minor Problems but I think that'll be subject of short messages on Discord.

Again: Thank both of you!

@mwydmuch
Copy link
Collaborator

mwydmuch commented Jan 11, 2024

@sean-mulready happy to hear that. As @Miffyli said, you don't really need to measure the time by yourself if you need it, you can calculate it based on the state.tic variable, every tic = 1/35 of a second.

Also, if you want your players to have a smooth experience, you should use the ASYNC_SPECTATOR mode, which will progress the game at a normal speed without waiting for the Python controller to respond to every single frame.
Then you can analyze the data in the normal mode at the speed you need to process/extract what you need.

Here I prepare a quick draft of the scripts to demonstrate the idea:

The script for recording the game, it just allows to play specific scenarios and the number of episodes:

import os
import vizdoom as vzd

# Config
scenario = "basic"  # Scenario to play
episodes = 10  # Number of episodes to play
sub_id = "01"  # Test subject


game = vzd.DoomGame()

# Load the scenario config
game.load_config(f"{scenario}.cfg")

# Set some resolution that is pleasant for humans
game.set_screen_resolution(vzd.ScreenResolution.RES_1280X720)
game.set_render_hud(True)

# Use ASYNC_SPECTATOR mode for perfect real-time gaming experience
game.set_mode(vzd.Mode.ASYNC_SPECTATOR)
game.init()

# Recording
print("\nRECORDING EPISODES")
print("************************\n")

# Create a directory for recordings
os.mkdir("recordings")

# Play the specified number of episodes
for i in range(episodes):
    recording_file = f"recordings/{scenario}_sub={sub_id}_epi={i}_rec.lmp"
    game.new_episode(recording_file)

    while not game.is_episode_finished():
        a = game.advance_action()  # Do nothing, just advance action till the end of the episode

    # Report the end of the episode
    print(f"Episode {i} finished. Saved to file {recording_file}")
    print("Total reward:", game.get_total_reward())
    print("************************\n")

game.new_episode()  # This is currently required to ensure the proper stopping and saving of the recording from the last episode (It's a bug I just discovered. I will fix it soon).
game.close()

Now that we have the recordings, we can analyze them this way:

import vizdoom as vzd
from time import sleep

# Config
scenario = "basic"  # Scenario to play
episodes = 10  # Number of episodes to play
sub_id = "01"  # Test subject


game = vzd.DoomGame()

# Use the same config (this is important)
game.load_config(f"{scenario}.cfg")

# New render settings for the replay, since you are not using the screen buffer, set the resolution to smaller to make processing faster
game.set_screen_resolution(vzd.ScreenResolution.RES_320X240)
game.set_render_hud(False)

# Enable additional information that is needed for the analysis

# Enables information about all objects present in the current episode/level.
game.set_objects_info_enabled(True)

# Enables information about all sectors (map layout).
game.set_sectors_info_enabled(True)

#(...) and some other things you need


# The game mode doesn't really matter in this case, so we don't set it and just init the game
game.init()

print("\nREPLAYING THE EPISODES")
print("************************\n")

for i in range(episodes):
    recording_file = f"recordings/{scenario}_sub={sub_id}_epi={i}_rec.lmp"
    game.replay_episode(recording_file)

    while not game.is_episode_finished():
        # Get a state
        s = game.get_state()

        # Proceed to the next state
        game.advance_action()

        # Retrieve the last actions, and the reward
        a = game.get_last_action()  # The example wrongly stated that it's not possible, but we actually implemented it later
        r = game.get_last_reward()

        # Do your analysis here, or dump the date to another format you would like to work with
        print(f"Tic #{s.tic}") 
        # The game will wait for you, so do it as long as you need, you will see that tic count will always increment by 1
        sleep(1) # To simulate a hard work of your script

    print("Episode", i, "finished.")
    print("Total reward:", game.get_total_reward())
    print("************************")

game.close()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants