|
1 | 1 | #include "pyunrealsdk/pch.h" |
2 | 2 | #include "pyunrealsdk/base_bindings.h" |
| 3 | +#include "pyunrealsdk/logging.h" |
3 | 4 | #include "pyunrealsdk/unreal_bindings/uenum.h" |
4 | 5 | #include "pyunrealsdk/unreal_bindings/wrapped_struct.h" |
| 6 | +#include "unrealsdk/config.h" |
5 | 7 | #include "unrealsdk/unreal/classes/uclass.h" |
6 | 8 | #include "unrealsdk/unreal/classes/uenum.h" |
7 | 9 | #include "unrealsdk/unreal/classes/uobject.h" |
@@ -85,6 +87,87 @@ UClass* evaluate_class_arg(const std::variant<UClass*, std::wstring>& cls_arg) { |
85 | 87 | return cls_ptr; |
86 | 88 | } |
87 | 89 |
|
| 90 | +/** |
| 91 | + * @brief Recursively merges two toml tables. |
| 92 | + * |
| 93 | + * @param base The base table. Modified in place. |
| 94 | + * @param overrides The overrides tables. |
| 95 | + */ |
| 96 | +void recursive_merge_table(py::dict& base, const py::dict& overrides) { |
| 97 | + for (auto [key, value] : overrides) { |
| 98 | + if (py::isinstance<py::dict>(value)) { |
| 99 | + if (base.contains(key)) { |
| 100 | + auto base_value = base[key]; |
| 101 | + if (py::isinstance<py::dict>(base_value)) { |
| 102 | + auto base_dict = base_value.cast<py::dict>(); |
| 103 | + auto overrides_dict = value.cast<py::dict>(); |
| 104 | + recursive_merge_table(base_dict, overrides_dict); |
| 105 | + continue; |
| 106 | + } |
| 107 | + } |
| 108 | + } |
| 109 | + base[key] = value; |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +/** |
| 114 | + * @brief Creates the config dict, and adds it to the given module. |
| 115 | + * |
| 116 | + * @param mod The module to add the config dict to. |
| 117 | + */ |
| 118 | +void create_and_add_config_dict(py::module_& mod) { |
| 119 | + // We want to copy the full, merged, unrealsdk config into Python. |
| 120 | + // Only a few basic accessors into the config dict are exposed, since we can't safely expose |
| 121 | + // the toml objects across dlls. |
| 122 | + // Properly converting toml from the C++ side into Python is also a bit awkward, with all the |
| 123 | + // different types. |
| 124 | + |
| 125 | + // Instead, to get everything in Python more easily, we'll just load the config files directly |
| 126 | + // in Python to begin with. |
| 127 | + |
| 128 | + auto load_config_file = [](const std::filesystem::path&& path) -> py::object { |
| 129 | + try { |
| 130 | + const py::dict globals{"path"_a = path}; |
| 131 | + py::exec( |
| 132 | + "import tomllib\n" |
| 133 | + "with path.open(\"rb\") as file:\n" |
| 134 | + " config = tomllib.load(file)\n", |
| 135 | + globals); |
| 136 | + return globals["config"]; |
| 137 | + |
| 138 | + } catch (const std::exception& ex) { |
| 139 | + LOG(ERROR, "Failed to load {}", path.string()); |
| 140 | + logging::log_python_exception(ex); |
| 141 | + return py::none{}; |
| 142 | + } |
| 143 | + }; |
| 144 | + |
| 145 | + auto base_config = load_config_file(unrealsdk::config::get_base_config_file_path()); |
| 146 | + auto user_config = load_config_file(unrealsdk::config::get_user_config_file_path()); |
| 147 | + |
| 148 | + if (!py::isinstance<py::dict>(base_config)) { |
| 149 | + if (py::isinstance<py::dict>(user_config)) { |
| 150 | + // Only user config got loaded |
| 151 | + mod.attr("config") = user_config; |
| 152 | + return; |
| 153 | + } |
| 154 | + // No config files got loaded, just create a default dict |
| 155 | + mod.attr("config") = py::dict{}; |
| 156 | + return; |
| 157 | + } |
| 158 | + if (!py::isinstance<py::dict>(user_config)) { |
| 159 | + // Only base config got loaded |
| 160 | + mod.attr("config") = base_config; |
| 161 | + return; |
| 162 | + } |
| 163 | + |
| 164 | + // Both configs were loaded, we need to merge them |
| 165 | + auto base_dict = base_config.cast<py::dict>(); |
| 166 | + auto user_dict = user_config.cast<py::dict>(); |
| 167 | + recursive_merge_table(base_dict, user_dict); |
| 168 | + mod.attr("config") = base_dict; |
| 169 | +} |
| 170 | + |
88 | 171 | } // namespace |
89 | 172 |
|
90 | 173 | void register_base_bindings(py::module_& mod) { |
@@ -251,6 +334,8 @@ void register_base_bindings(py::module_& mod) { |
251 | 334 | "Returns:\n" |
252 | 335 | " The loaded `Package` object.", |
253 | 336 | "name"_a, "flags"_a = 0); |
| 337 | + |
| 338 | + create_and_add_config_dict(mod); |
254 | 339 | } |
255 | 340 |
|
256 | 341 | } // namespace pyunrealsdk |
|
0 commit comments