Skip to content
Snippets Groups Projects
hooks.py 23.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • """
    You can add callbacks at specific points in the environment.
    This "hook" mechanism is defined here.
    
    
    You can add hooks via the `environment_config` under `hook_callbacks` and the here defined
    
    `hooks_via_callback_class` function.
    
    Each hook get different kwargs. But `env` with the environment object and `hook_ref` with the name of the hook are
    always present.
    
    # Code Documentation
    """
    
    
    from __future__ import annotations
    
    from abc import abstractmethod
    
    from collections import defaultdict
    
    from functools import partial
    
    from typing import Callable, Any, TYPE_CHECKING, Type
    
    if TYPE_CHECKING:
    
        from cooperative_cuisine.environment import Environment
    
    # --- environment.py ---
    
    
    ITEM_INFO_LOADED = "item_info_load"
    
    """Called after the item info is loaded and stored in the env attribute `item_info`.
    
    Args:
        item_info (str): the string content of the item info config file.
        parsed_item_info (dict[str, ItemInfo]): the parsed item info.
    """
    
    
    LAYOUT_FILE_PARSED = "layout_file_parsed"
    """After the layout file was parsed. No additional kwargs. Everything is stored in the env."""
    
    
    ITEM_INFO_CONFIG = "item_info_config"
    
    """Called before the item info is parsed.
    
    Args:
        item_info_config (str): the string content of the item info config file.
    """
    
    ENV_INITIALIZED = "env_initialized"
    
    """At the end of the __init__ method. 
    
    Args:
        environment_config (str): the str content of the environment config file.
        layout_config (str): the str content of the layout config file.
        seed (int): the random seed.
        env_start_time_worldtime (datetime): the "real" world time when the hook was called.
    """
    
    
    PRE_PERFORM_ACTION = "pre_perform_action"
    
    """Before an action is performed / entered into the environment. `action` kwarg with the entered action.
    
    Args:
        action (Action): the action that will be performed.
    """
    
    POST_PERFORM_ACTION = "post_perform_action"
    
    """After an action is performed / entered into the environment. `action` kwarg with the entered action.
    
    Args:
        action (Action): the action that was performed.
    """
    
    
    # TODO Pre and Post Perform Movement
    
    PLAYER_ADDED = "player_added"
    
    """Called after a player has been added. Kwargs: `player` and `pos`.
    
    Args:
        player (Player): the new player object that was created.
        pos (npt.NDArray[float]): the 2d position of the player.
    """
    
    
    GAME_ENDED_STEP = "game_ended_step"
    
    """When the step function is called but the game ended already. No kwargs."""
    
    
    PRE_STATE = "pre_state"
    
    """Called before the state is calculated for a player.
    
    Args:
        player_id (str): The id of the player for which the state is calculated.
    """
    
    fheinrich's avatar
    fheinrich committed
    PRE_STEP = "pre_step"
    
    """When the step function is called before the execution happens. Currently not active (Optimization).
    
    Args:
        passed_time (timedelta): the time elapsed since the last step call.
    """
    
    
    
    fheinrich's avatar
    fheinrich committed
    POST_STEP = "post_step"
    
    """When the step function is called after the execution.
    
    Args:
        passed_time (timedelta): the time elapsed since the last step call.
    """
    
    STATE_DICT = "state_dict"
    
    """When the dictionary (containing the state) is generated.
     
     Args:
        state (dict): the current state for a player. Should follow the `StateRepresentation`.
        player_id (str): the id of the player.
    
     """
    
    
    
    JSON_STATE = "json_state"
    
    """When the json string was generated for a state.
    
    Args:
        json_data (str): the json string containing the state.
        player_id (str): the id of the player.
    """
    
    
    PRE_RESET_ENV_TIME = "pre_reset_env_time"
    
    """Before the time of the environment is reset.
    
    Args:
        env_time (datetime): the env time before the reset.
    """
    
    
    POST_RESET_ENV_TIME = "post_reset_env_time"
    
    """After the time of the environment is reset.
    
    Args:
        env_time (datetime): the env time after the reset.
    """
    
    # --- counters.py ---
    
    PRE_COUNTER_PICK_UP = "pre_counter_pick_up"
    
    """Before the pick up on a counter is executed.
    
    Args:
        counter (Counter): the counter from which to pick up.
        on_hands (bool): if the picked up item is put on the free player hands (True) or on a cooking equipment (False).
        player (str): player id.
    """
    
    
    POST_COUNTER_PICK_UP = "post_counter_pick_up"
    
    """After the pick up on a counter is executed.
    
    Args:
        counter (Counter): the counter from which to pick up.
        on_hands (bool): if the picked up item is put on the free player hands (True) or on a cooking equipment (False).
        return_this (Item | None): what will be put on the player (holding)
        player (str): player id.
    
        player_holding: (Item | None): the item that the player is holding.
    
    PRE_DISPENSER_PICK_UP = "pre_dispenser_pick_up"
    
    """Before the pick up on a dispenser happens. `PRE_COUNTER_PICK_UP` is not called.
    
    Args:
        counter (Counter): the counter(dispenser) from which to pick up.
        on_hands (bool): if the picked up item is put on the free player hands (True) or on a cooking equipment (False).
        player (str): player id.
    """
    
    
    POST_DISPENSER_PICK_UP = "post_dispenser_pick_up"
    
    """After the new item on a dispenser is generated when a pick up happens.
    
    Args:
        counter (Counter): the counter/dispenser from which to pick up.
        on_hands (bool): if the picked up item is put on the free player hands (True) or on a cooking equipment (False).
        return_this (Item | None): what will be put on the player (holding) - the new item / the item that was on the counter.
        player (str): player id.
    
        player_holding (Item): What the player was holding. 
    
    
    PRE_PLATE_DISPENSER_PICK_UP = "pre_plate_dispenser_pick_up"
    
    """Before the pick up on a plate dispenser happens. `PRE_COUNTER_PICK_UP` and `PRE_DISPENSER_PICK_UP` is not called.
    
    Args:
        counter (Counter): the plate dispenser from which to pick up.
        on_hands (bool): if the picked up item is put on the free player hands (True) or on a cooking equipment (False).
        player (str): player id.
    """
    
    
    
    POST_PLATE_DISPENSER_PICK_UP = "post_plate_dispenser_pick_up"
    
    """After the pick up from a plate dispenser is executed.
    
    Args:
        counter (Counter): the counter from which to pick up.
        player (str): player id.
    
        on_hands (bool): if the picked up item is put on the free player hands (True) or on a cooking equipment (False).
        returned_item (Item | None): what will be put on the player (holding)
    
    """
    
    PRE_COUNTER_DROP_OFF = "pre_counter_drop_off"
    """Before the drop off on a counter is executed.
    
    Args:
        item (Item): the item to drop on the counter.
        equipment (Item | None | deque[Item]): what is currently on the counter.
        counter (Counter): the counter from which to drop off.
        player (str): player id.
    """
    
    POST_COUNTER_DROP_OFF = "post_counter_drop_off"
    """Drop off on a free counter (nothing on it).
    
    Args:
        counter (Counter): the counter from which to drop off.
        player (str): player id.
        item (Item): the item to drop on the counter.
    """
    
    DROP_OFF_ON_COOKING_EQUIPMENT = "drop_off_on_cooking_equipment"
    """When drop off on a counter with a cooking equipment is on it. Before the combining happens.
    
    Args:
        item (Item): the item to drop on the counter.
        equipment (Item | None | deque[Item]): what is currently on the counter.
        counter (Counter): the counter from which to drop off.
    
        occupied_before (Item): What was on the counter before the drop off.
    
        player (str): player id.
        return_this (Item | None): what will be put on the player (holding)
    """
    
    PICK_UP_ON_COOKING_EQUIPMENT = "pick_up_on_cooking_equipment"
    """When pick up from a counter when the player is holding a cooking equipment. Before the combining happens.
    
    Args:
        return_this (Item): the item returned from the counter.
        occupied_by (Item): What is now in the counter.
        counter (Counter): the counter from which to pick up.
        player (str): player id.
    
        player_holding (Item): What the player was holding.
    
    PRE_PLATE_DISPENSER_DROP_OFF = "pre_plate_dispenser_drop_off"
    
    """Before something is dropped on a plate dispenser.
    
    Args:
        counter (Counter): the plate dispenser from which to drop off.
        player (str): player id.
        item (Item): the item to drop on the counter.
    """
    
    
    POST_PLATE_DISPENSER_DROP_OFF = "post_plate_dispenser_drop_off"
    
    """Something is dropped on a free plate dispenser.
    
    Args:
        counter (Counter): the counter from which to drop off.
        player (str): player id.
        item (Item): the item to drop on the counter.
    """
    
    
    DROP_OFF_ON_COOKING_EQUIPMENT_PLATE_DISPENSER = (
        "drop_off_on_cooking_equipment_plate_dispenser"
    )
    """After something is dropped on a plate dispenser and is combined with the top plate.
    
    Args:
        counter (Counter): the counter from which to drop off.
        player (str): player id.
        item (Item): the item to drop on the counter.
        equipment (Item | None | deque[Item]): the cooking equipment that combined the items.
    
        return_this (Item | None): what will be put on the player (holding)
    
    DISPENSER_ITEM_RETURNED = "dispenser_item_returned"
    
    """Undo the pickup on a dispenser. (Drop off action.)
    
    Args:
        counter (Counter): the dispenser.
        player (str): player id.
        item (Item): the item that is returned.
    """
    
    CUTTING_BOARD_PROGRESS = "cutting_board_progress"
    
    """Valid cutting board interaction step. 
    
    Args:
        counter (Counter): the cutting board which is used to chop the ingredient.
        player (str): player id.
        percent (float): how much percent is added in the progress call.
        passed_time (timedelta): passed time since the last step call in the environment-
    """
    
    
    CUTTING_BOARD_100 = "cutting_board_100"
    
    """Chopping finished on a cutting board.
    
    Args:
        counter (Counter): the cutting board.
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
        item (Item): the item that was chopped.
    
        player (str): player id.
    
        before: (Item): What the item was before.
    
    PROGRESS_STARTED = "progress_started"
    
    """Progress on a cooking equipment is started.
    
    Args:
        item (CookingEquipment): the cooking equipment that does the progress.
    """
    
    PROGRESS_FINISHED = "progress_finished"
    
    """Progress on a cooking equipment is finished. (Does not include fire.)
    
    Args:
    
        before (CookingEquipment): the cooking equipment before the progress.
        item (CookingEquipment): the cooking equipment after the progress.
    
    
    PRE_SERVING = "pre_serving"
    
    """Called if player wants to drop off on `ServingWindow`.
    
    Args:
        counter (ServingWindow): the serving window the player wants to serve on.
        item (Item): the item to serve (on the player's hand).
        env_time (datetime): current env time.
        player (str): the player id.
    """
    
    POST_SERVING = "post_serving"
    
    """Serving was successful.
    
    Args:
        counter (ServingWindow): the serving window on which the item was served on.
        item (Item): the item that was served. (Plate)
        env_time (datetime): current env time.
        player (str): the player id.
    """
    
    NO_SERVING = "no_serving"
    
    """Serving was not successful. (Rejected from the order manager)
     
    Args:
        counter (ServingWindow): the serving window the player wanted to serve on.
        item (Item): the item that cannot be serves (now still on the player's hand).
        env_time (datetime): current env time.
        player (str): the player id.
    """
    
    SCORE_CHANGED = "score_changed"
    """The score was changed.
    
    Args:
        increase (float): the increase of the score.
        score (float): the new (increased) score.
        info (str): additional info.
    """
    
    PLATE_OUT_OF_KITCHEN_TIME = "plate_out_of_kitchen_time"
    
    """A plate is out of the kitchen (after successful serving).
    
    Args:
        time_plate_to_add (datetime): the time the plate will arrive back in the kitchen
    """
    
    DIRTY_PLATE_ARRIVES = "dirty_plate_arrives"
    
    """A plate arrived at the kitchen (some time after serving)
    
    Args:
        counter (Counter): 
    """
    
    
    TRASHCAN_USAGE = "trashcan_usage"
    
    """Successful usage of the trashcan.
    
    Args:
        counter (Trashcan): the trashcan used.
        item (Item): the item the player holding. (If it is a `CookingEquipmeent` only content_list is removed).
        player (str): the player id.
    """
    
    
    ADDED_PLATE_TO_SINK = "added_plate_to_sink"
    
    """Dirty Plate dropped on the sink.
    
    Args:
        counter (Sink): the sink, target of the drop off.
        item (Plate): the dirty plate.
        player (str): the player id.
    """
    
    DROP_ON_SINK_ADDON = "drop_on_sink_addon"
    
    """Something is dropped on the sink addon and combined with the top plate.
    
    Args:
        counter (SinkAddon): the target counter of the drop off.
        item (Item): the item which is combined with the top plate.
    
        occupied_by: (Item | None): What the sink addon is occupied by. 
    
    PICK_UP_FROM_SINK_ADDON = "pick_up_from_sink_addon"
    
    """Player picks up the top plate.
    
    Args:
        player (str): the player id.
        occupied_by (Plate): the top plate that is picked up.
        counter (SinkAddon): the counter class from it picked up.
    """
    
    PLATE_CLEANED = "plate_cleaned"
    
    """The player finished cleaning a plate.
    
    Plate is transferred to the SinkAddon afterwards.
    
    Args:
        counter (Sink): The sink on which the plate was cleaned.
        player (str): the player id.
    
        plate (Item): the plate that was cleaned.
    
        plate_before (Item): the plate before cleaning.
    
    ON_ITEM_TRANSITION = "on_item_transition"
    
    """A new Effect appears (Fire starts).
    
    Args:
        item (CookingEquipment): the pot, pan, ... on which the fire starts.
        result (Effect): the fire effect that is now active on the equipment.
        seconds (float/int): how long it took to create the fire.
    """
    
    CONTENT_READY = "content_ready"
    
    """A meal is ready on a cooking equipment.
    
    Args:
        before (CookingEquipment): the cooking equipment.
    
        result (str): Name of the meal.
    
    PLATED_MEAL = "plated_meal"
    """A ready meal was plated on a Plate"""
    
    
    COOKING_FINISHED = "cooking_finished"
    
    
    SERVE_NOT_ORDERED_MEAL = "serve_not_ordered_meal"
    
    """If a meal does not match to an order but `serve_not_ordered_meals`is active.
    
    Args:
        meal (Item): the meal that is served.
        meal_name (str): equivalent to meal.name. The name of the meal.
    
    SERVE_WITHOUT_PLATE = "serve_without_plate"
    
    """Somehow tried to serve without a plate. Will not be served.
    
    Args:
        item (Item): The item that the player has in his hands.
    
    
    COMPLETED_ORDER = "completed_order"
    
    """A meal was served that completed an order.
    
    Args:
        order (Order): the order that was fulfilled.
        meal (Item): The meal that was served.
    
        item (Item): The plate that was used to serve the meal (with content).
    
        relative_order_time (timedelta): the time that the player needed to fulfill the order.
    
        remaining_time_ratio (float): the ratio of the remaining time of the order relative to order duration.
    
    INIT_ORDERS = "init_orders"
    
    """The initial orders were generated.
    
    Args:
        init_orders (list[Order]): the init orders.
    """
    
    NEW_ORDERS = "new_orders"
    
    """New orders (not init orders) were generated.
    
    Args:
        new_orders (list[Order]): the new orders.
    """
    
    ORDER_EXPIRED = "order_expired"
    
    """An order expired (took too long).
    
    Args:
        order (Order): the order that expired.
    """
    
    # --- environment.py --- but player related.
    
    
    ACTION_ON_NOT_REACHABLE_COUNTER = "action_on_not_reachable_counter"
    
    """Player wants to interact or pick/drop but no counter is in reach.
    
    Args:
        action (Action): the action with the player id.
        counter (Counter): closest but not reachable counter.
    
    ACTION_PUT = "action_put"
    
    """Player does the put (pick or drop) action
    
    Args:
        action (Action): the action with the player id.
        counter (Counter): on which the put action is performed.
    
    ACTION_INTERACT_START = "action_interact_start"
    
    """Player starts interacting on a counter.
    
    Args:
        action (Action): the action with the player id
        counter (Counter): on which the player starts the interaction.
    
    ITEM_BURNED = "item_burned"  # MISSING
    
    NEW_FIRE = "new_fire"
    
    """New fire from cooking equipment (Does not include spreading).
    
    Args:
        target (Counter|Item): on which the fire was created.
    """
    
    FIRE_EXTINGUISHED = "fire_extinguished"
    """Fire was extinguished.
    
    Args:
        target (Counter|Item): on which the fire was extinguished.
        player (str): the player id.
    """
    
    
    FIRE_SPREADING = "fire_spreading"
    
    """Fire spreads on other counter/items after some time.
    
    Args:
            target (Counter|Item): on which the fire is spreading.
    """
    
    PLAYERS_COLLIDE = "players_collide"
    
    """Every frame player collides.
    
    Args:
        collisions (npt.NDArray[bool]): the players which collide.
    """
    
    PLAYER_START_INTERACT = "player_start_interaction"
    
    """Same as `ACTION_INTERACT_START` but in the player.py
    
    Args:
        player (str): the player id.
        counter (Counter): the last interacted counter.
    
        item (Item): the item that the player is holding.
    
    PLAYER_END_INTERACT = "player_end_interact"
    
    """The interaction with a counter stopped. Either stopped by the player through button press or move away.
    
    Args:
        player (str): the player id.
        counter (Counter): the last interacted counter.
    
        item (Item): the item that the player is holding.
    
    
    # -- extra --
    ADDITIONAL_STATE_UPDATE = "additional_state_update"
    """Update of the additional content of the state.
    
    Args:
        update (dict[str, Any]): update of the additional state content. 
    """
    
        """Represents a collection of hooks and provides methods to register callbacks for hooks and invoke the callbacks when hooks are triggered.
    
    
        Attributes:
            hooks (defaultdict[list]): A defaultdict containing lists of callbacks for each hook reference.
            env (any): The environment variable passed to the Hooks instance.
    
        Methods:
    
                Initializes a new instance of Hooks.
    
    
            __call__(self, hook_ref: str, **kwargs)
    
                Invokes the callbacks associated with the specified hook reference.
        """
    
    
        def __init__(self, env: Environment):
    
            """Constructor for the Hooks object.
    
            Args:
                env (Environment): The environment object to be referenced.
            """
            self.hooks: dict[str, list[Callable]] = defaultdict(list)
            """The hook callbacks per hook_ref."""
            self.env: Environment = env
            """Reference to the environment object."""
    
    
        def __call__(self, hook_ref, **kwargs):
            for callback in self.hooks[hook_ref]:
                callback(hook_ref=hook_ref, env=self.env, **kwargs)
    
        def register_callback(self, hook_ref: str | list[str], callback: Callable):
    
            """Register a callback for a hook which is called when the hook is touched during execution.
    
            Args:
                hook_ref: A string or a list of strings representing the reference(s) of the hook(s) to register the callback for.
                callback: A callable object (function or method) to be registered as a callback.
            """
    
            if isinstance(hook_ref, (tuple, list, set)):  # TODO check for iterable
                for ref in hook_ref:
                    self.hooks[ref].append(callback)
            else:
                self.hooks[hook_ref].append(callback)
    
    
    
    def print_hook_callback(text, env, **kwargs):
    
        """Dummy hook callback. Print env time and hook ref."""
    
        #print(env.env_time, text)
    
    class HookCallbackClass:
    
        """
        Class: HookCallbackClass
    
        Represents a callback class for hook events.
    
        Attributes:
        - name: A string representing the name of the callback.
        - env: An Environment object representing the environment in which the callback is being executed.
    
        Methods:
        - __init__(self, name: str, env: Environment, **kwargs):
            Initializes a new instance of HookCallbackClass.
    
        - __call__(self, hook_ref: str, env: Environment, **kwargs):
            Abstract method that executes the callback logic when called.
    
        Note:
        - This class is meant to be subclassed and the __call__ method implemented according to specific needs.
        - The **kwargs parameter allows for additional arguments to be passed to the callback function.
    
        Usage Example:
    
            # Create an instance of HookCallbackClass
            callback = HookCallbackClass("my_callback", my_env)
    
            # Subclass HookCallbackClass and implement the __call__ method
            class MyCallback(HookCallbackClass):
                def __call__(self, hook_ref: str, env: Environment, **kwargs):
                    # Add custom callback logic here
    
            # Create an instance of the subclass
            my_callback = MyCallback("my_callback", my_env)
    
            # Call the callback
            my_callback("hook_reference", my_env)
    
        def __init__(self, name: str, env: Environment, **kwargs):
    
            """Constructor  of HookCallbackClass.
    
            Args:
                name: A string representing the name of the callback.
                env: An instance of the Environment class representing the reference to the environment.
                **kwargs: Additional keyword arguments.
            """
            self.name: str = name
    
            """The name of the callback."""
    
            """Reference to the environment."""
    
    
        @abstractmethod
        def __call__(self, hook_ref: str, env: Environment, **kwargs):
            ...
    
    
    def hooks_via_callback_class(
        name: str,
        env: Environment,
        hooks: list[str],
        callback_class: Type[HookCallbackClass],
        callback_class_kwargs: dict[str, Any],
    ):
    
        """Setup hook callback class.
    
    
        Args:
            name: A string representing the name of the callback class instance.
            env: An instance of the Environment class.
            hooks: A list of strings representing the hooks for which the callback class instance needs to be registered.
            callback_class: A type representing the class of the callback instance to be created.
            callback_class_kwargs: A dictionary containing additional keyword arguments to be passed to the callback class constructor.
        """
    
        recorder = callback_class(name=name, env=env, **callback_class_kwargs)
        for hook in hooks:
            env.register_callback_for_hook(hook, recorder)
    
    
    
    def add_dummy_callbacks(env):
    
        """Checking the hooks-callback functionality.
    
        Args:
            env: The environment object that represents the system environment.
    
        This method adds dummy callbacks to the given environment object. Each callback is registered for a specific hook using the `register_callback_for_hook` method of the environment.
    
        The callbacks are defined using the `partial` function from the `functools` module. This allows us to pass additional arguments to the callback while registering it. The `print_hook
        *_callback` function is used as the callback function, and it prints a message to the console.
    
        Here are the hooks and corresponding messages that are registered:
    
        1. SERVE_NOT_ORDERED_MEAL: Prints the message "You tried to serve a meal that was not ordered!"
        2. SINK_START_INTERACT: Prints the message "You started to use the Sink!"
        3. COMPLETED_ORDER: Prints the message "You completed an order!"
        4. TRASHCAN_USAGE: Prints the message "You used the trashcan!"
    
        These dummy callbacks can be used for testing or demonstration purposes.
        """
    
        env.register_callback_for_hook(
            SERVE_NOT_ORDERED_MEAL,
            partial(
                print_hook_callback,
                text="You tried to served a meal that was not ordered!",
            ),
        )
        env.register_callback_for_hook(
            COMPLETED_ORDER,
            partial(
                print_hook_callback,
                text="You completed an order!",
            ),
        )
        env.register_callback_for_hook(
            TRASHCAN_USAGE,
            partial(
                print_hook_callback,
                text="You used the trashcan!",
            ),
        )