16#include <dotnetpp/dotnetpp.h>
30enum class recompile_command :
int
37std::chrono::milliseconds check_interval(50);
40std::atomic<recompile_command> needs_recompile{};
41std::mutex container_mutex;
42std::vector<std::string> needs_to_recompile;
43std::atomic<uint64_t> compilation_version{};
45std::atomic_bool debug_mode{
true};
47auto print_assembly_info(
const dotnet::assembly& assembly)
50 auto refs = assembly.dump_references();
52 ss << fmt::format(
" ----- References -----");
54 for(
const auto& ref : refs)
56 ss << fmt::format(
"\n{}", ref);
61 auto types = assembly.get_types();
64 ss << fmt::format(
" ----- Types -----");
66 for(
const auto&
type : types)
68 ss << fmt::format(
"\n{}",
type.get_fullname());
69 ss << fmt::format(
"\n sizeof {}",
type.get_sizeof());
70 ss << fmt::format(
"\n alignof {}",
type.get_alignof());
73 auto attribs =
type.get_attributes();
74 for(
const auto& attrib : attribs)
76 ss << fmt::format(
"\n - Attribute : {}", attrib.get_type().get_fullname());
80 auto fields =
type.get_fields();
81 for(
const auto& field : fields)
83 ss << fmt::format(
"\n - Field : {}", field.get_name());
85 auto attribs = field.get_attributes();
86 for(
const auto& attrib : attribs)
88 ss << fmt::format(
"\n -- Attribute : {}", attrib.get_type().get_fullname());
92 auto properties =
type.get_properties();
93 for(
const auto& prop : properties)
95 ss << fmt::format(
"\n - Property : {}", prop.get_name());
97 auto attribs = prop.get_attributes();
98 for(
const auto& attrib : attribs)
100 ss << fmt::format(
"\n -- Attribute : {}", attrib.get_type().get_fullname());
111 auto frame = dotnet::extract_relevant_stack_frame(e.what());
112 if(!
frame.file_name.empty())
118 APPLOG_ERROR_LOC(loc.file_name(),
int(loc.line()), loc.function_name(), e.what());
124 auto from_debug_info = from;
125 from_debug_info.concat(
".mdb");
126 auto from_comments_xml = from;
127 from_comments_xml.replace_extension(
".xml");
129 auto to_debug_info = to;
130 to_debug_info.concat(
".mdb");
131 auto to_comments_xml = to;
132 to_comments_xml.replace_extension(
".xml");
135 fs::copy_file(from, to, fs::copy_options::overwrite_existing, er);
136 fs::copy_file(from_debug_info, to_debug_info, fs::copy_options::overwrite_existing, er);
137 fs::copy_file(from_comments_xml, to_comments_xml, fs::copy_options::overwrite_existing, er);
139 fs::remove(from, er);
140 fs::remove(from_debug_info, er);
141 fs::remove(from_comments_xml, er);
146 bool is_deploy_mode = ctx.has<
deploy>();
148 dotnet::compiler_paths result;
152#if DOTNETPP_BACKEND_MONO
154 result.assembly_dir = fs::absolute(mono_dir /
"lib").string();
155 result.config_dir = fs::absolute(mono_dir /
"etc").string();
162 auto bridge_dir =
fs::resolve_protocol(
"engine:/" + std::string(dotnet::managed_runtime_dir()));
163 if(fs::exists(bridge_dir, ec))
165 result.assembly_dir = fs::absolute(bridge_dir).string();
172 if(fs::exists(dotnet_dir, ec))
174 result.config_dir = fs::absolute(dotnet_dir).string();
180 const auto& names = dotnet::get_common_library_names();
181 const auto& library_paths = dotnet::get_common_library_paths();
182 const auto& config_paths = dotnet::get_common_config_paths();
184 for(
size_t i = 0; i < library_paths.size(); ++i)
186 const auto& library_path = library_paths.at(i);
187 const auto& config_path = config_paths.at(i);
188 std::vector<std::string> paths{library_path};
189 auto found_library = fs::find_library(names, paths);
191 if(!found_library.empty())
193 result.assembly_dir = fs::path(library_path).make_preferred().string();
194 result.config_dir = fs::path(config_path).make_preferred().string();
202 const auto& names = dotnet::get_common_executable_names();
203 const auto& paths = dotnet::get_common_executable_paths();
205 result.msc_executable = fs::find_program(names, paths).make_preferred().string();
209 APPLOG_TRACE(
"Assembly path - {}", result.assembly_dir);
224 dotnet::interpreter_config config;
225#if defined(UNRAVEL_FORCE_DOTNET_INTERPRETER)
226 config.interp_mode = dotnet::interpreter_config::mode::forced;
229 if(parser.try_get(
"interpreter", mode) && mode ==
"forced")
231 config.interp_mode = dotnet::interpreter_config::mode::forced;
242 "CoreCLR interpreter mode (auto|forced).");
245auto validate_paths(
const dotnet::compiler_paths& paths,
bool is_deploy_mode) ->
bool
247#if DOTNETPP_BACKEND_MONO
248 (void)is_deploy_mode;
249 return !paths.assembly_dir.empty() && !paths.config_dir.empty() && !paths.msc_executable.empty();
258 return !paths.msc_executable.empty();
264 APPLOG_TRACE(
"{}::{}", hpp::type_name_str(*
this), __func__);
266 auto& ev = ctx.get_cached<
events>();
267 ev.
on_frame_update.connect(sentinel_,
this, &script_system::on_frame_update);
268 ev.on_frame_fixed_update.connect(sentinel_,
this, &script_system::on_frame_fixed_update);
269 ev.on_frame_update.connect(sentinel_, -100000,
this, &script_system::on_frame_late_update);
271 ev.on_play_end.connect(sentinel_, 1000,
this, &script_system::on_play_end);
272 ev.on_pause.connect(sentinel_, 100,
this, &script_system::on_pause);
273 ev.on_resume.connect(sentinel_, -100,
this, &script_system::on_resume);
274 ev.on_skip_next_frame.connect(sentinel_, -100,
this, &script_system::on_skip_next_frame);
276 auto mono_paths = find_dotnet_paths(ctx);
280#if DOTNETPP_BACKEND_MONO
282 "Failed to locate Mono C#. Please install it from - https://www.mono-project.com/download/stable/");
285 "Failed to locate the .NET runtime. Please install it from - https://dotnet.microsoft.com/download");
290 debug_config_.enable_debugging =
true;
292 dotnet::set_log_handler(
"info",
293 [](
const std::string& msg)
297 dotnet::set_log_handler(
"trace",
298 [](
const std::string& msg)
302 dotnet::set_log_handler(
"warning",
303 [](
const std::string& msg)
307 dotnet::set_log_handler(
"error",
308 [](
const std::string& msg)
315 bind_internal_calls(ctx);
321 if(!load_engine_domain(ctx,
true))
326 catch(
const dotnet::exception& e)
335#if DOTNETPP_BACKEND_MONO
337 "Failed to initialize Mono C#. Please install it from - https://www.mono-project.com/download/stable/");
341 "Failed to initialize the .NET runtime. Please install it from - https://dotnet.microsoft.com/download");
348 APPLOG_TRACE(
"{}::{}", hpp::type_name_str(*
this), __func__);
351 unload_engine_domain();
360 debug_config_.address = address;
361 debug_config_.port = port;
362 debug_config_.loglevel = loglevel;
369 bool is_deploy_mode = ctx.has<
deploy>();
371 if(!is_deploy_mode && recompile)
375 debug = get_script_debug_mode();
378 if(!create_compilation_job(ctx,
"engine", debug).get())
384 domain_ = std::make_unique<dotnet::domain>(
"Unravel.Engine");
385 dotnet::domain::set_current_domain(domain_.get());
390 copy_compiled_lib(engine_script_lib_temp, engine_script_lib);
392 auto assembly = domain_->get_assembly(engine_script_lib.string());
395 APPLOG_TRACE(
"-------------------------------------------------------------");
396 APPLOG_TRACE(
"Loading domain {} with version: {}", domain_->get_name(), domain_->get_version());
397 APPLOG_TRACE(
"-------------------------------------------------------------");
399 cache_.update_manager_type = assembly.get_type(
"Unravel.Core",
"SystemManager");
400 cache_.component_type = assembly.get_type(
"Unravel.Core",
"Component");
401 cache_.script_component_type = assembly.get_type(
"Unravel.Core",
"ScriptComponent");
402 cache_.ui_event_manager_type = assembly.get_type(
"Unravel.Core",
"UIEventManager");
405 cache_.update_method = cache_.update_manager_type.get_method(
"internal_n2m_update", 1);
406 cache_.fixed_update_method = cache_.update_manager_type.get_method(
"internal_n2m_fixed_update", 1);
407 cache_.late_update_method = cache_.update_manager_type.get_method(
"internal_n2m_late_update", 0);
409 cache_.set_entity_method = cache_.component_type.get_method(
"internal_n2m_set_entity", 1);
410 cache_.on_create_method = cache_.script_component_type.get_method(
"internal_n2m_on_create", 0);
411 cache_.on_enable_method = cache_.script_component_type.get_method(
"internal_n2m_on_enable", 0);
412 cache_.on_disable_method = cache_.script_component_type.get_method(
"internal_n2m_on_disable", 0);
413 cache_.on_start_method = cache_.script_component_type.get_method(
"internal_n2m_on_start", 0);
414 cache_.on_destroy_method = cache_.script_component_type.get_method(
"internal_n2m_on_destroy", 0);
415 cache_.on_sensor_enter_method = cache_.script_component_type.get_method(
"internal_n2m_on_sensor_enter", 2);
416 cache_.on_sensor_exit_method = cache_.script_component_type.get_method(
"internal_n2m_on_sensor_exit", 2);
417 cache_.on_collision_enter_method =
418 cache_.script_component_type.get_method(
"internal_n2m_on_collision_enter", 2);
419 cache_.on_collision_exit_method =
420 cache_.script_component_type.get_method(
"internal_n2m_on_collision_exit", 2);
422 cache_.ui_dispatch_event_method =
423 cache_.ui_event_manager_type.get_method(
"InternalDispatchEvent", 1);
433 APPLOG_TRACE(
"-------------------------------------------------------------");
434 APPLOG_TRACE(
"Unloading domain {} with version: {}", domain_->get_name(), domain_->get_version());
435 APPLOG_TRACE(
"-------------------------------------------------------------");
438 dotnet::domain::set_current_domain(
nullptr);
445 bool is_deploy_mode = ctx.has<
deploy>();
449 if(!is_deploy_mode && recompile)
451 result &= create_compilation_job(ctx,
"app", get_script_debug_mode()).get();
453 has_compilation_errors_ = !result;
456 app_domain_ = std::make_unique<dotnet::domain>(
"Unravel.App");
457 dotnet::domain::set_current_domain(app_domain_.get());
462 copy_compiled_lib(app_script_lib_temp, app_script_lib);
476 if(fs::exists(app_script_lib, ec))
478 APPLOG_TRACE(
"-------------------------------------------------------------");
479 APPLOG_TRACE(
"Loading domain {} with version: {}", app_domain_->get_name(), app_domain_->get_version());
480 APPLOG_TRACE(
"-------------------------------------------------------------");
483 auto assembly = app_domain_->get_assembly(app_script_lib.string());
486 app_cache_.scriptable_component_types.clear();
487 if(!has_compilation_errors_)
489 app_cache_.scriptable_component_types = assembly.get_types_derived_from(get_scriptable_component_base_type());
494 auto engine_assembly = get_engine_assembly();
495 for(
const auto&
type : app_cache_.scriptable_component_types)
497 auto fullname =
type.get_fullname();
498 if(engine_assembly.get_type(fullname).valid())
500 APPLOG_WARNING(
"Script type '{}' is defined in both the engine and the app scripts. "
501 "The app version will be used; rename it to avoid ambiguity.",
507 catch(
const dotnet::exception& e)
525 APPLOG_TRACE(
"-------------------------------------------------------------");
526 APPLOG_TRACE(
"Unloading domain {} with version: {}", app_domain_->get_name(), app_domain_->get_version());
527 APPLOG_TRACE(
"-------------------------------------------------------------");
530 dotnet::domain::set_current_domain(domain_.get());
564 if(!app_domain_ || !domain_)
570 create_call_ = call_progress::started;
574 for(
auto entity : entities)
583 create_call_ = call_progress::finished;
587 for(
auto entity : entities)
603 catch(
const dotnet::exception& e)
612 if(!app_domain_ || !domain_)
618 create_call_ = call_progress::started;
623 [&](
auto e,
auto&& comp)
629 create_call_ = call_progress::finished;
634 [&](
auto e,
auto&& comp)
647 catch(
const dotnet::exception& e)
655 APPLOG_TRACE(
"{}::{}", hpp::type_name_str(*
this), __func__);
657 if(!app_domain_ || !domain_)
664 auto& registry = *scn.registry;
670 registry.on_construct<
active_component>().connect<&on_create_active_component>();
671 registry.on_destroy<
active_component>().connect<&on_destroy_active_component>();
680 APPLOG_TRACE(
"{}::{}", hpp::type_name_str(*
this), __func__);
684 auto& registry = *scn.registry;
701 registry.on_construct<
active_component>().disconnect<&on_create_active_component>();
702 registry.on_destroy<
active_component>().disconnect<&on_destroy_active_component>();
704 registry.on_construct<
script_component>().disconnect<&on_create_component>();
705 registry.on_destroy<
script_component>().disconnect<&on_destroy_component>();
722 on_frame_update(ctx, step);
729 if(!play.is_active())
731 check_for_recompile(ctx, dt,
true);
738 if(!app_domain_ || !domain_)
745 auto& registry = *scn.registry;
747 registry.view<script_component>().each(
748 [&](
auto e,
auto&& comp)
750 comp.process_pending_deletions();
752 if(play.is_simulation_running() && registry.all_of<active_component>(e))
763 uint64_t frame_count{};
766 if(play.is_simulation_running() && !play.is_paused())
770 if(play.frames_running() == 0)
776 auto time_scale =
sim.get_time_scale();
779 data.time = elapsed_time_.count();
780 data.delta_time = dt.count();
781 data.time_scale = time_scale;
782 data.frame_count =
sim.get_frame();
787 auto method_thunk = dotnet::make_method_invoker<void(update_data)>(cache_.
update_method);
794 catch(
const dotnet::exception& e)
798 is_updating_ =
false;
809 if(!app_domain_ || !domain_)
815 auto& scn = ec.get_scene();
816 auto& registry = *scn.registry;
818 registry.view<script_component>().each(
819 [&](
auto e,
auto&& comp)
821 comp.process_pending_deletions();
826 float fixed_delta_time{};
829 if(play.is_simulation_running() && play.frames_running() > 0 && dt > delta_t::zero())
832 data.fixed_delta_time = dt.count();
837 auto method_thunk = dotnet::make_method_invoker<void(update_data)>(cache_.
fixed_update_method);
842 catch(
const dotnet::exception& e)
857 if(!app_domain_ || !domain_)
863 auto& scn = ec.get_scene();
864 auto& registry = *scn.registry;
866 if(play.is_simulation_running() && play.frames_running() > 0 && dt > delta_t::zero())
874 catch(
const dotnet::exception& e)
883 dt = std::max(delta_t::zero(), dt);
894 return app_cache_.scriptable_component_types;
905 return domain_->get_assembly(engine_script_lib.string());
911 return app_domain_->get_assembly(app_script_lib.string());
921 type = app_domain_->get_type(fullname);
923 if(!
type.valid() && domain_)
925 type = domain_->get_type(fullname);
936 type = app_domain_->get_type(name_space,
name);
938 if(!
type.valid() && domain_)
940 type = domain_->get_type(name_space,
name);
947 return create_call_ == call_progress::finished;
956 return dotnet::is_debugger_attached();
961 time_since_last_check_ += dt;
963 if(time_since_last_check_ >= check_interval || needs_recompile == recompile_command::compile_now)
965 time_since_last_check_ = {};
967 recompile_command should_recompile = needs_recompile.exchange(recompile_command::none);
969 if(should_recompile != recompile_command::none)
971 auto container = []()
973 std::lock_guard<std::mutex> lock(container_mutex);
974 auto result = std::move(needs_to_recompile);
978 compilation_jobs_.clear();
980 compilation_version++;
982 auto current_version = compilation_version.load();
983 for(
const auto& protocol : container)
986 .then(tpp::this_thread::get_id(),
987 [
this, &ctx, protocol, emit_callback, current_version](
auto f)
995 if(play.is_simulation_running())
1000 if(compilation_version > current_version)
1005 has_compilation_errors_ = !
f.get();
1006 if(!has_compilation_errors_)
1008 ev.on_script_recompile(ctx, protocol, current_version);
1012 compilation_jobs_.emplace_back(std::move(job));
1022 check_for_recompile(ctx, 100s,
false);
1024 auto jobs = std::move(compilation_jobs_);
1026 for(
auto& job : jobs)
1032auto script_system::create_compilation_job(
rtti::context& ctx,
1033 const std::string& protocol,
1034 bool debug) -> tpp::job_future<bool>
1045 return thr.pool->schedule(
1047 [&am, flags, protocol]()
1049 auto key = get_lib_data_key(protocol);
1050 auto output = get_lib_temp_compiled_key(protocol);
1061 needs_recompile = now ? recompile_command::compile_now : recompile_command::compile_at_schedule;
1063 std::lock_guard<std::mutex> lock(container_mutex);
1064 if(std::find(std::begin(needs_to_recompile), std::end(needs_to_recompile), protocol) ==
1065 std::end(needs_to_recompile))
1067 needs_to_recompile.emplace_back(protocol);
1084 return protocol +
"-script.dll";
1119 comp->on_sensor_enter(other, manifolds);
1121 catch(
const dotnet::exception& e)
1142 comp->on_sensor_exit(other, manifolds);
1144 catch(
const dotnet::exception& e)
1163 comp->on_collision_enter(
b, manifolds,
true);
1171 comp->on_collision_enter(
a, manifolds,
false);
1175 catch(
const dotnet::exception& e)
1194 comp->on_collision_exit(
b, manifolds,
true);
1202 comp->on_collision_exit(
a, manifolds,
false);
1206 catch(
const dotnet::exception& e)
1214 return has_compilation_errors_;
void set_optional(const std::string &name, const std::string &alternative, T defaultValue, const std::string &description="", bool dominant=false)
Manages assets, including loading, unloading, and storage.
auto get_assets(const std::string &group={}) const -> std::vector< asset_handle< T > >
Gets all assets in a specified group.
Class that contains core data for audio listeners. There can only be one instance of it per scene.
std::chrono::duration< float > delta_t
#define APPLOG_WARNING(...)
#define APPLOG_ERROR(...)
#define APPLOG_TRACE_PERF_NAMED(T, name)
#define APPLOG_ERROR_LOC(FILE_LOC, LINE_LOC, FUNC_LOC,...)
#define APPLOG_TRACE(...)
auto get_data_directory(const std::string &prefix={}) -> std::string
auto get_type() -> const std::string &
auto get_compiled_directory(const std::string &prefix={}) -> std::string
path resolve_protocol(const path &_path)
Given the specified path/filename, resolve the final full filename. This will be based on either the ...
void stop_all(const std::string &scope)
Stops all actions within the specified scope.
void update(seq_id_t id, duration_t delta)
Updates the elapsed duration of a specific action.
Hash specialization for batch_key to enable use in std::unordered_map.
auto compile< script_library >(asset_manager &am, const fs::path &key, const fs::path &output, uint32_t flags) -> bool
auto on_load(entt::registry ®) -> typename on_load_bus< T >::sink_t
auto validate_paths(const dotnet::compiler_paths &paths, bool is_deploy_mode) -> bool
auto select_interpreter_config(const cmd_line::parser &parser) -> dotnet::interpreter_config
#define APP_SCOPE_PERF(name_literal)
Create a scoped performance timer that records to the timeline profiler. Only accepts string literals...
Manages the entity-component-system (ECS) operations for the ACE framework.
auto get_scene() -> scene &
Gets the current scene.
hpp::event< void(rtti::context &, delta_t)> on_frame_update
dotnet::method update_method
dotnet::method fixed_update_method
dotnet::method late_update_method
dotnet::type script_component_type
static auto is_debugger_attached() -> bool
static auto get_lib_name(const std::string &protocol) -> std::string
auto get_scriptable_component_base_type() const -> dotnet::type
static void on_load_component(entt::registry &r, entt::entity e)
Called when a script component is loaded.
auto is_create_called() const -> bool
auto get_type(const std::string &name_space, const std::string &name) const -> dotnet::type
static auto get_lib_data_key(const std::string &protocol) -> std::string
static auto get_script_debug_mode() -> bool
void unload_engine_domain()
auto is_update_called() const -> bool
static auto find_dotnet_paths(const rtti::context &ctx) -> dotnet::compiler_paths
script_system(rtti::context &ctx, cmd_line::parser &parser)
static auto get_lib_compiled_key(const std::string &protocol) -> std::string
static auto get_lib_temp_compiled_key(const std::string &protocol) -> std::string
static void set_script_debug_mode(bool debug)
auto load_engine_domain(rtti::context &ctx, bool recompile) -> bool
auto has_compilation_errors() const -> bool
void on_collision_enter(entt::handle a, entt::handle b, const std::vector< manifold_point > &manifolds)
auto get_app_assembly() const -> dotnet::assembly
static void on_create_component(entt::registry &r, entt::entity e)
Called when a physics component is created.
static void set_needs_recompile(const std::string &protocol, bool now=false)
void set_debug_config(const std::string &address, uint32_t port, uint32_t loglevel)
static void copy_compiled_lib(const fs::path &from, const fs::path &to)
static void on_destroy_active_component(entt::registry &r, entt::entity e)
auto load_app_domain(rtti::context &ctx, bool recompile) -> bool
auto get_all_scriptable_components() const -> const std::vector< dotnet::type > &
static void on_create_active_component(entt::registry &r, entt::entity e)
auto get_type_by_fullname(const std::string &fullname) const -> dotnet::type
static void log_exception(const dotnet::exception &e, const hpp::source_location &loc=hpp::source_location::current())
void on_play_begin(rtti::context &ctx)
Called when playback begins.
auto deinit(rtti::context &ctx) -> bool
auto get_engine_assembly() const -> dotnet::assembly
void on_collision_exit(entt::handle a, entt::handle b, const std::vector< manifold_point > &manifolds)
void wait_for_jobs_to_finish(rtti::context &ctx)
void on_sensor_enter(entt::handle sensor, entt::handle other, const std::vector< manifold_point > &manifolds)
void on_sensor_exit(entt::handle sensor, entt::handle other, const std::vector< manifold_point > &manifolds)
static void on_destroy_component(entt::registry &r, entt::entity e)
Called when a physics component is destroyed.
auto init(rtti::context &ctx, const cmd_line::parser &parser) -> bool
std::vector< GitHubAsset > assets