Skip to content

Commit

Permalink
Give each game_ticket_packet to the agent exactly once (#30)
Browse files Browse the repository at this point in the history
* Give each game_ticket_packet to the agent once.
Added nicer error handling (using traceback)

Sample game_ticket_packet at the Nyquist rate and
don't call the bot when the game_tick_packet did not change.

I used this to identify the problem:
https://gist.github.com/DomNomNom/cfb59ea18138e3d1244d8f1881ac97ba
Before:
https://cdn.discordapp.com/attachments/348659150793736193/403871599083651072/unknown.png
After:
https://cdn.discordapp.com/attachments/348659150793736193/403878905838043139/unknown.png

* Call the agent even when the game is paused
  • Loading branch information
DomNomNom authored and tarehart committed Jan 20, 2018
1 parent 256da37 commit 956f7bb
Showing 1 changed file with 50 additions and 40 deletions.
90 changes: 50 additions & 40 deletions bot_manager.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import bot_input_struct as bi
import ctypes
from ctypes import *
from datetime import datetime
from datetime import datetime, timedelta
import game_data_struct as gd
import importlib
import mmap
import os
import rate_limiter
import sys
import imp
import traceback

OUTPUT_SHARED_MEMORY_TAG = 'Local\\RLBotOutput'
INPUT_SHARED_MEMORY_TAG = 'Local\\RLBotInput'
RATE_LIMITED_ACTIONS_PER_SECOND = 60
GAME_TICK_PACKET_REFRESHES_PER_SECOND = 120 # 2*60. https://en.wikipedia.org/wiki/Nyquist_rate
MAX_AGENT_CALL_PERIOD = timedelta(seconds=1.0/30) # Minimum call rate when paused.
REFRESH_IN_PROGRESS = 1
REFRESH_NOT_IN_PROGRESS = 0
MAX_CARS = 10
Expand Down Expand Up @@ -44,7 +46,9 @@ def run(self):


# Create Ratelimiter
r = rate_limiter.RateLimiter(RATE_LIMITED_ACTIONS_PER_SECOND)
r = rate_limiter.RateLimiter(GAME_TICK_PACKET_REFRESHES_PER_SECOND)
last_tick_game_time = None # What the tick time of the last observed tick was
last_call_real_time = datetime.now() # When we last called the Agent

# Find car with same name and assign index
for i in range(MAX_CARS):
Expand All @@ -62,52 +66,58 @@ def run(self):
# Run until main process tells to stop
while not self.terminateEvent.is_set():
before = datetime.now()

try:
# Read from game data shared memory
game_data_shared_memory.seek(0) # Move to beginning of shared memory
ctypes.memmove(ctypes.addressof(lock), game_data_shared_memory.read(ctypes.sizeof(lock)), ctypes.sizeof(lock)) # dll uses InterlockedExchange so this read will return the correct value!

if lock.value != REFRESH_IN_PROGRESS:
game_data_shared_memory.seek(4, os.SEEK_CUR) # Move 4 bytes past error code
ctypes.memmove(ctypes.addressof(game_tick_packet), game_data_shared_memory.read(ctypes.sizeof(gd.GameTickPacket)),ctypes.sizeof(gd.GameTickPacket)) # copy shared memory into struct

# Reload the Agent if it has been modified.
new_module_modification_time = os.stat(agent_module.__file__).st_mtime
if new_module_modification_time != last_module_modification_time:
last_module_modification_time = new_module_modification_time
print ("Reloading Agent: " + agent_module.__file__)
imp.reload(agent_module)
agent = agent_module.Agent(self.name, self.team, self.index)

# Call agent
controller_input = agent.get_output_vector(game_tick_packet)

# Write all player inputs
player_input.fThrottle = controller_input[0]
player_input.fSteer = controller_input[1]
player_input.fPitch = controller_input[2]
player_input.fYaw = controller_input[3]
player_input.fRoll = controller_input[4]
player_input.bJump = controller_input[5]
player_input.bBoost = controller_input[6]
player_input.bHandbrake = controller_input[7]
# Read from game data shared memory
game_data_shared_memory.seek(0) # Move to beginning of shared memory
ctypes.memmove(ctypes.addressof(lock), game_data_shared_memory.read(ctypes.sizeof(lock)), ctypes.sizeof(lock)) # dll uses InterlockedExchange so this read will return the correct value!

if lock.value != REFRESH_IN_PROGRESS:
game_data_shared_memory.seek(4, os.SEEK_CUR) # Move 4 bytes past error code
ctypes.memmove(ctypes.addressof(game_tick_packet), game_data_shared_memory.read(ctypes.sizeof(gd.GameTickPacket)),ctypes.sizeof(gd.GameTickPacket)) # copy shared memory into struct

# Run the Agent only if the gameInfo has updated.
tick_game_time = game_tick_packet.gameInfo.TimeSeconds
should_call_while_paused = datetime.now() - last_call_real_time >= MAX_AGENT_CALL_PERIOD
if tick_game_time != last_tick_game_time or should_call_while_paused:
last_tick_game_time = tick_game_time
last_call_real_time = datetime.now()

try:
# Reload the Agent if it has been modified.
new_module_modification_time = os.stat(agent_module.__file__).st_mtime
if new_module_modification_time != last_module_modification_time:
last_module_modification_time = new_module_modification_time
print('Reloading Agent: ' + agent_module.__file__)
imp.reload(agent_module)
agent = agent_module.Agent(self.name, self.team, self.index)


# Call agent
controller_input = agent.get_output_vector(game_tick_packet)

if not controller_input:
raise Exception('Agent "{}" did not return a player_input tuple.'.format(agent_module.__file__))

# Write all player inputs
player_input.fThrottle = controller_input[0]
player_input.fSteer = controller_input[1]
player_input.fPitch = controller_input[2]
player_input.fYaw = controller_input[3]
player_input.fRoll = controller_input[4]
player_input.bJump = controller_input[5]
player_input.bBoost = controller_input[6]
player_input.bHandbrake = controller_input[7]

except Exception as e:
traceback.print_exc()

# Workaround for windows streams behaving weirdly when not in command prompt
sys.stdout.flush()
sys.stderr.flush()

except Exception as e:
print(e)

# Ratelimit here
after = datetime.now()
# print('Latency of ' + self.name + ': ' + str(after - before))

r.acquire(after-before)

# If terminated, send callback
self.callbackEvent.set()



0 comments on commit 956f7bb

Please sign in to comment.