From a03b1511ac741e88938087ba0cf1a50a846a298a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Schr=C3=B6der?= <fschroeder@techfak.uni-bielefeld.de> Date: Thu, 21 Dec 2023 14:08:47 +0100 Subject: [PATCH] use an env time instead of datetime now. Pass the passed time via the step function. Add a test --- overcooked_simulator/counters.py | 16 +++++---- .../overcooked_environment.py | 13 ++++++-- overcooked_simulator/simulation_runner.py | 9 ++--- overcooked_simulator/utils.py | 7 ++++ tests/test_start.py | 33 ++++++++++++++++--- 5 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 overcooked_simulator/utils.py diff --git a/overcooked_simulator/counters.py b/overcooked_simulator/counters.py index 9694c824..616ad694 100644 --- a/overcooked_simulator/counters.py +++ b/overcooked_simulator/counters.py @@ -5,6 +5,8 @@ from collections import deque from datetime import datetime, timedelta from typing import TYPE_CHECKING, Optional +from overcooked_simulator.utils import create_init_env_time + if TYPE_CHECKING: from overcooked_simulator.overcooked_environment import ( GameScore, @@ -97,7 +99,7 @@ class CuttingBoard(Counter): self.progressing = False super().__init__(pos) - def progress(self): + def progress(self, passed_time: timedelta, now: datetime): """Called by environment step function for time progression""" if self.progressing: if isinstance(self.occupied_by, CuttableItem): @@ -194,6 +196,7 @@ class PlateDispenser(Counter): self.plate_config = {"plate_delay": [5, 10]} self.plate_config.update(plate_config) self.next_plate_time = datetime.max + self.env_time = create_init_env_time() # is overwritten in progress anyway self.setup_plates() def pick_up(self, on_hands: bool = True): @@ -223,7 +226,8 @@ class PlateDispenser(Counter): def update_plate_out_of_kitchen(self): """Is called from the serving window to add a plate out of kitchen.""" - time_plate_to_add = datetime.now() + timedelta( + # not perfect identical to datetime.now but based on framerate enough. + time_plate_to_add = self.env_time + timedelta( seconds=np.random.uniform( low=self.plate_config["plate_delay"][0], high=self.plate_config["plate_delay"][1], @@ -251,9 +255,9 @@ class PlateDispenser(Counter): ] ) - def progress(self): + def progress(self, passed_time: timedelta, now: datetime): """Check if plates arrive from outside the kitchen and add a dirty plate accordingly""" - now = datetime.now() + self.env_time = now if self.next_plate_time < now: idx_delete = [] for i, times in enumerate(self.out_of_kitchen_timer): @@ -294,7 +298,7 @@ class Stove(Counter): else: return self.occupied_by.can_combine(item) - def progress(self): + def progress(self, passed_time: timedelta, now: datetime): """Called by environment step function for time progression""" if ( self.occupied_by @@ -311,7 +315,7 @@ class Sink(Counter): self.sink_addon: SinkAddon = sink_addon self.occupied_by = deque() - def progress(self): + def progress(self, passed_time: timedelta, now: datetime): """Called by environment step function for time progression""" if self.progressing: if self.occupied_by: diff --git a/overcooked_simulator/overcooked_environment.py b/overcooked_simulator/overcooked_environment.py index 8a51ecf4..d049f913 100644 --- a/overcooked_simulator/overcooked_environment.py +++ b/overcooked_simulator/overcooked_environment.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging import random +from datetime import timedelta from pathlib import Path from threading import Lock @@ -23,6 +24,7 @@ from overcooked_simulator.counters import ( ) from overcooked_simulator.game_items import ItemInfo, ItemType from overcooked_simulator.player import Player +from overcooked_simulator.utils import create_init_env_time log = logging.getLogger(__name__) @@ -131,6 +133,8 @@ class Environment: self.world_width: int = environment_config["world_width"] self.world_height: int = environment_config["world_height"] + self.env_time = create_init_env_time() + def load_item_info(self) -> dict[str, ItemInfo]: with open(self.item_info_path, "r") as file: item_lookup = yaml.safe_load(file) @@ -429,14 +433,15 @@ class Environment: ) return collisions_lower or collisions_upper - def step(self): + def step(self, passed_time: timedelta): """Performs a step of the environment. Affects time based events such as cooking or cutting things, orders and time limits. """ + self.env_time += passed_time with self.lock: for counter in self.counters: if isinstance(counter, (CuttingBoard, Stove, Sink, PlateDispenser)): - counter.progress() + counter.progress(passed_time=passed_time, now=self.env_time) def get_state(self): """Get the current state of the game environment. The state here is accessible by the current python objects. @@ -484,3 +489,7 @@ class Environment: return list( filter(lambda counter: isinstance(counter, counter_type), self.counters) ) + + def reset_env_time(self): + self.env_time = create_init_env_time() + log.debug(f"Reset env time to {self.env_time}") diff --git a/overcooked_simulator/simulation_runner.py b/overcooked_simulator/simulation_runner.py index 795b61e6..c1f32c80 100644 --- a/overcooked_simulator/simulation_runner.py +++ b/overcooked_simulator/simulation_runner.py @@ -1,5 +1,6 @@ import logging import time +from datetime import timedelta from threading import Thread import numpy as np @@ -44,9 +45,9 @@ class Simulator(Thread): super().__init__() - def step(self): + def step(self, passed_time: timedelta): """One simulation step of the environment.""" - self.env.step() + self.env.step(passed_time) def enter_action(self, action: Action): """Takes an action and executes it in the environment. @@ -96,10 +97,10 @@ class Simulator(Thread): """Starts the simulator thread. Runs in a loop until stopped.""" overslept_in_ns = 0 - + self.env.reset_env_time() while not self.finished: step_start = time.time_ns() - self.step() + self.step(timedelta(seconds=overslept_in_ns / 1_000_000_000)) step_duration = time.time_ns() - step_start time_to_sleep_ns = self.preferred_sleep_time_ns - ( diff --git a/overcooked_simulator/utils.py b/overcooked_simulator/utils.py new file mode 100644 index 00000000..4e4ab7ad --- /dev/null +++ b/overcooked_simulator/utils.py @@ -0,0 +1,7 @@ +from datetime import datetime + + +def create_init_env_time(): + return datetime( + year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0 + ) diff --git a/tests/test_start.py b/tests/test_start.py index a8fc103e..ac5a63ba 100644 --- a/tests/test_start.py +++ b/tests/test_start.py @@ -1,4 +1,5 @@ import time +from datetime import timedelta import numpy as np import pytest @@ -6,8 +7,9 @@ import pytest from overcooked_simulator import ROOT_DIR from overcooked_simulator.counters import Counter, CuttingBoard from overcooked_simulator.game_items import CuttableItem -from overcooked_simulator.overcooked_environment import Action +from overcooked_simulator.overcooked_environment import Action, Environment from overcooked_simulator.simulation_runner import Simulator +from overcooked_simulator.utils import create_init_env_time layouts_folder = ROOT_DIR / "game_content" / "layouts" @@ -51,9 +53,12 @@ def test_simulator_frequency(): def __init__(self): self.c = 0 - def step(self): + def step(self, passed_time): self.c += 1 + def reset_env_time(self): + pass + frequency = 2000 running_time_seconds = 2 @@ -68,7 +73,6 @@ def test_simulator_frequency(): time.sleep(running_time_seconds) sim.stop() - print(sim.env.c) accepted_tolerance = 0.02 lower = frequency * running_time_seconds * (1 - accepted_tolerance) upper = frequency * running_time_seconds * (1 + accepted_tolerance) @@ -216,7 +220,7 @@ def test_processing(): counter_pos = np.array([100, 100]) counter = CuttingBoard(counter_pos) - sim.env.counters = [counter] + sim.env.counters.append(counter) tomato = CuttableItem(name="Tomato", item_info=None) sim.register_player("p1", np.array([100, 150])) @@ -243,3 +247,24 @@ def test_processing(): sim.enter_action(button_up) sim.stop() + + +def test_time_passed(): + np.random.seed(42) + env = Environment( + ROOT_DIR / "game_content" / "environment_config.yaml", + layouts_folder / "empty.layout", + ROOT_DIR / "game_content" / "item_info.yaml", + ) + env.reset_env_time() + passed_time = timedelta(seconds=10) + env.step(passed_time) + assert ( + env.env_time == create_init_env_time() + passed_time + ), "Env time needs to be updated via the step function" + + passed_time_2 = timedelta(seconds=12) + env.step(passed_time_2) + assert ( + env.env_time == create_init_env_time() + passed_time + passed_time_2 + ), "Env time needs to be updated via the step function" -- GitLab