Skip to content

Commit b6484da

Browse files
SIM RFC: AI selector backend (UBC-Thunderbots#3569)
* Issue functionality * Reverted test framework * Added AI CONFIG class * Added some comments * Added comment * Added spec and reverted bad merge change * Moved all functionality to runtime loader * Changed all constant names + added AI config functionality * Got rid of a todo * Added cache, fixed some of the specs * Removed old constants * [pre-commit.ci lite] apply automatic fixes * Added caching functionality * Replaced AI wording with Runtimes * Fixed creation of toml file in setup software * [pre-commit.ci lite] apply automatic fixes * Added small explanation for caching * Completely messed up RuntimeConfig class; should be fixed now * [pre-commit.ci lite] apply automatic fixes * Added small function is valid runtime * Added small function is valid runtime * Added some more spec clarifications. Sorry I really should doublecheck these things before pushing... * [pre-commit.ci lite] apply automatic fixes * Simplified is_valid_runtime * Added logging messages for if the retrieved runtime is invalid * Something something sudo mkdir something (added small function in runtime_manager.py to make directory) * Made config a dataclass * [pre-commit.ci lite] apply automatic fixes * Reverted creating directory stuff * Deleted toml creation in setup software sh * [pre-commit.ci lite] apply automatic fixes * Cleaned runtime loader. Will add list of things done in a bit * [pre-commit.ci lite] apply automatic fixes * Got rid of a space * Fixed spec --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent c43cf6f commit b6484da

4 files changed

Lines changed: 124 additions & 36 deletions

File tree

Lines changed: 113 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,138 @@
1+
from tomllib import TOMLDecodeError
12
from software.thunderscope.constants import RuntimeManagerConstants
3+
from dataclasses import dataclass
4+
import os
5+
import tomllib
6+
import logging
7+
8+
9+
@dataclass
10+
class RuntimeConfig:
11+
"""Data class to store the paths of the two binaries"""
12+
13+
chosen_blue_path: str = RuntimeManagerConstants.DEFAULT_BINARY_PATH
14+
"""Blue runtime path"""
15+
16+
chosen_yellow_path: str = RuntimeManagerConstants.DEFAULT_BINARY_PATH
17+
"""Yellow runtime path"""
218

319

4-
# TODO: #3557
520
class RuntimeLoader:
621
"""Delegate class for handling local runtimes and managing runtime selection"""
722

8-
def __init__(self):
9-
self.cached_runtimes = None
10-
1123
def fetch_installed_runtimes(self) -> list[str]:
12-
"""Fetches the list of available runtimes from the local disk
13-
:return: A list of names for available runtimes
24+
"""Fetches the list of available runtimes, including our FullSystem, from the local disk. Makes the folder
25+
in our local disk if it does not exist yet.
26+
:return: A list of names for available runtimes, or just a list with our FullSystem if no available runtimes
27+
could be found
1428
"""
15-
if self.cached_runtimes:
16-
return self.cached_runtimes.keys()
17-
else:
18-
return []
29+
runtime_list = [RuntimeManagerConstants.DEFAULT_BINARY_NAME]
30+
31+
if not os.path.isdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH):
32+
os.mkdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH)
33+
# Check for all executable files in the folder, and add its name to the list
34+
for file_name in os.listdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH):
35+
file_path = os.path.join(
36+
RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH, file_name
37+
)
38+
if os.access(file_path, os.X_OK):
39+
runtime_list.append(file_name)
40+
41+
# Cache external runtimes
42+
return runtime_list
1943

2044
def load_existing_runtimes(self, yellow_runtime: str, blue_runtime: str) -> None:
2145
"""Loads the yellow and blue runtimes specified by saving them in the local disk.
2246
:param blue_runtime: Unique name of the blue runtime to set
2347
:param yellow_runtime: Unique name of the yellow runtime to set
2448
"""
25-
config = {}
49+
config = RuntimeConfig(
50+
self._return_runtime_path(blue_runtime),
51+
self._return_runtime_path(yellow_runtime),
52+
)
2653
self._set_runtime_config(config)
27-
pass
2854

29-
def fetch_runtime_config(self) -> dict[str, str]:
30-
"""Fetches the runtime configuration from the local disk.
31-
:return: Returns the runtime configuration as a map
55+
def fetch_runtime_config(self) -> RuntimeConfig:
56+
"""Fetches the runtime configuration from the local disk. If the blue/yellow configuration is invalid,
57+
returns the default runtime configuration for blue/yellow
58+
:return: Returns the runtime configuration as a RuntimeConfig
3259
"""
33-
return {
34-
RuntimeManagerConstants.RUNTIME_CONFIG_BLUE_KEY: RuntimeManagerConstants.DEFAULT_BINARY_PATH,
35-
RuntimeManagerConstants.RUNTIME_CONFIG_YELLOW_KEY: RuntimeManagerConstants.DEFAULT_BINARY_PATH,
36-
}
60+
# Create default FullSystem pair with our FullSystem binaries
61+
config = RuntimeConfig()
62+
63+
try:
64+
with open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "rb") as file:
65+
selected_runtime_dict = tomllib.load(file)
66+
# Get the persisted blue path, or replace with the default arrangement if it doesn't exist
67+
toml_blue_path = selected_runtime_dict.get(
68+
RuntimeManagerConstants.RUNTIME_CONFIG_BLUE_KEY,
69+
RuntimeManagerConstants.DEFAULT_BINARY_PATH,
70+
)
71+
if self._is_valid_runtime(toml_blue_path):
72+
config.chosen_blue_path = toml_blue_path
73+
# Get the persisted yellow path, or replace with the default arrangement if it doesn't exist
74+
toml_yellow_path = selected_runtime_dict.get(
75+
RuntimeManagerConstants.RUNTIME_CONFIG_YELLOW_KEY,
76+
RuntimeManagerConstants.DEFAULT_BINARY_PATH,
77+
)
78+
if self._is_valid_runtime(toml_yellow_path):
79+
config.chosen_yellow_path = toml_yellow_path
80+
except (FileNotFoundError, PermissionError, TOMLDecodeError):
81+
logging.warning(
82+
f"Failed to read TOML file at: {RuntimeManagerConstants.RUNTIME_CONFIG_PATH}"
83+
)
3784

38-
def _create_runtime_config(self) -> None:
39-
"""Creates the runtime configuration file on disk and throws an error upon failure."""
40-
pass
85+
return config
4186

42-
def _set_runtime_config(self, config: dict[str, str]) -> None:
87+
def _set_runtime_config(self, config: RuntimeConfig) -> None:
4388
"""Sets/persists the runtime configuration file on disk and creates the configuration
4489
file if it doesn't exist.
4590
:param config: The runtime configuration containing
4691
- color_runtime : absolute path of external runtime, or
4792
- color_runtime : relative path of DEFAULT_BINARY_PATH
4893
"""
49-
pass
94+
blue_path = config.chosen_blue_path
95+
yellow_path = config.chosen_yellow_path
96+
97+
"""Format in TOML as:
98+
blue_path_to_binary: '<runtime path>'
99+
yellow_path_to_binary: '<runtime path>'"""
100+
101+
selected_runtimes = (
102+
f'{RuntimeManagerConstants.RUNTIME_CONFIG_BLUE_KEY} = "{blue_path}"\n'
103+
f'{RuntimeManagerConstants.RUNTIME_CONFIG_YELLOW_KEY} = "{yellow_path}"'
104+
)
105+
106+
# create a new config file if it doesn't exist, and write in the format above to it
107+
with open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "w") as file:
108+
file.write(selected_runtimes)
109+
110+
def _return_runtime_path(self, selected_runtime: str) -> str:
111+
"""Returns the absolute path of a binary given its name, or the path of our default FullSystem
112+
if the binary is not valid.
113+
:param selected_runtime: the name of the selected runtime binary
114+
:return: the absolute path of the binary as a string, or the relative path of our FullSystem
115+
"""
116+
file_path = os.path.join(
117+
RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH, selected_runtime
118+
)
119+
# Default to our full system if it is selected or the selected binary isn't a valid runtime
120+
if (
121+
selected_runtime == RuntimeManagerConstants.DEFAULT_BINARY_NAME
122+
or not self._is_valid_runtime(file_path)
123+
):
124+
return RuntimeManagerConstants.DEFAULT_BINARY_PATH
125+
# Remove leading and trailing white space and return
126+
return file_path.strip()
127+
128+
def _is_valid_runtime(self, runtime_path: str) -> bool:
129+
"""Returns if the path exists and if it is an executable. Logs a warning if it is not valid.
130+
:param runtime_path the path to check
131+
:return: whether it is a valid runtime or not
132+
"""
133+
if os.path.isfile(runtime_path) and os.access(runtime_path, os.X_OK):
134+
return True
135+
logging.warning(
136+
f"The runtime retrieved at {runtime_path} is not a valid runtime."
137+
)
138+
return False

src/software/thunderscope/binary_context_managers/runtime_manager.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from software.thunderscope.binary_context_managers.runtime_installer import (
22
RuntimeInstaller,
33
)
4-
from software.thunderscope.binary_context_managers.runtime_loader import RuntimeLoader
4+
from software.thunderscope.binary_context_managers.runtime_loader import (
5+
RuntimeLoader,
6+
RuntimeConfig,
7+
)
58

69

710
class RuntimeManager:
@@ -30,15 +33,15 @@ def fetch_installed_runtimes(self) -> list[str]:
3033
return self.runtime_loader.fetch_installed_runtimes()
3134

3235
def load_existing_runtimes(self, yellow_runtime: str, blue_runtime: str) -> None:
33-
"""Loads the runtimes of the specified name or throws an error upon failure.
36+
"""Loads the runtimes of the specified name or logs a warning upon failure.
3437
:param blue_runtime: name of the blue runtime to load
3538
:param yellow_runtime: name of the yellow runtime to load
3639
"""
3740
self.runtime_loader.load_existing_runtimes(yellow_runtime, blue_runtime)
3841

39-
def fetch_runtime_config(self) -> dict[str, str]:
42+
def fetch_runtime_config(self) -> RuntimeConfig:
4043
"""Fetches the runtime configuration from the local disk
41-
:return: Returns the runtime configuration as a map
44+
:return: Returns the runtime configuration as a RuntimeConfig
4245
"""
4346
return self.runtime_loader.fetch_runtime_config()
4447

src/software/thunderscope/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ class RuntimeManagerConstants:
394394
RUNTIME_CONFIG_BLUE_KEY = "blue_path_to_binary"
395395
RUNTIME_CONFIG_YELLOW_KEY = "yellow_path_to_binary"
396396
DEFAULT_BINARY_PATH = "software/unix_full_system"
397-
DEFAULT_BINARY_NAME = "localhost"
397+
DEFAULT_BINARY_NAME = "Current Fullsystem"
398398
EXTERNAL_RUNTIMES_PATH = "/opt/tbotspython/external_runtimes"
399399
RUNTIME_CONFIG_PATH = f"{EXTERNAL_RUNTIMES_PATH}/runtime_config.toml"
400400
INSTALL_URL = "https://api.github.com/repos/UBC-Thunderbots/Software/releases"

src/software/thunderscope/thunderscope_main.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
runtime_manager_instance,
1313
)
1414

15+
1516
protobuf_impl_type = api_implementation.Type()
1617
assert protobuf_impl_type == "upb", (
1718
f"Trying to use the {protobuf_impl_type} protobuf implementation. "
@@ -29,7 +30,6 @@
2930
from software.thunderscope.constants import (
3031
EstopMode,
3132
ProtoUnixIOTypes,
32-
RuntimeManagerConstants,
3333
)
3434
from software.thunderscope.estop_helpers import get_estop_config
3535
from software.thunderscope.proto_unix_io import ProtoUnixIO
@@ -446,9 +446,7 @@ def __ticker(tick_rate_ms: int) -> None:
446446
with Simulator(
447447
args.simulator_runtime_dir, args.debug_simulator, args.enable_realism
448448
) as simulator, FullSystem(
449-
path_to_binary=runtime_config[
450-
RuntimeManagerConstants.RUNTIME_CONFIG_BLUE_KEY
451-
],
449+
path_to_binary=runtime_config.chosen_blue_path,
452450
full_system_runtime_dir=args.blue_full_system_runtime_dir,
453451
debug_full_system=args.debug_blue_full_system,
454452
friendly_colour_yellow=False,
@@ -457,9 +455,7 @@ def __ticker(tick_rate_ms: int) -> None:
457455
running_in_realtime=(not args.ci_mode),
458456
log_level=args.log_level,
459457
) as blue_fs, FullSystem(
460-
path_to_binary=runtime_config[
461-
RuntimeManagerConstants.RUNTIME_CONFIG_YELLOW_KEY
462-
],
458+
path_to_binary=runtime_config.chosen_yellow_path,
463459
full_system_runtime_dir=args.yellow_full_system_runtime_dir,
464460
debug_full_system=args.debug_yellow_full_system,
465461
friendly_colour_yellow=True,

0 commit comments

Comments
 (0)