Unravel Engine C++ Reference
Loading...
Searching...
No Matches
script_component.cpp
Go to the documentation of this file.
2#include "logging/logging.h"
4
8
10#include <engine/engine.h>
11#include <engine/events.h>
13#include <dotnetpp/dotnetpp.h>
14
17
27#include <graphics/texture.h>
28#include <hpp/finally.hpp>
29
30namespace unravel
31{
32
33namespace
34{
35 struct script_component_loader_context
36 {
37 std::vector<script_component::script_object> script_objects;
38
39 auto get_script_object(size_t hash) -> script_component::script_object*
40 {
41 for(auto& obj : script_objects)
42 {
43 auto mono_obj = obj.pinned->get_object();
44
45 if(!mono_obj.valid())
46 {
47 APPLOG_ERROR("Script object is invalid for domain version: {}", obj.pinned->get_domain_version());
48 continue;
49 }
50 const auto& type = mono_obj.get_type();
51 if(!type.valid())
52 {
53 APPLOG_ERROR("Script object type is invalid for domain version: {}", obj.pinned->get_domain_version());
54 continue;
55 }
56 if(type.get_hash() == hash)
57 {
58 return &obj;
59 }
60 }
61 return nullptr;
62 }
63 };
64
65 thread_local script_component_loader_context* script_component_loader_ctx{};
66
67
68}
69
71{
72 entt::meta_factory<script_component>{}
73 .type("script_component"_hs)
75 entt::attribute{"name", "script_component"},
76 entt::attribute{"category", "SCRIPTING"},
77 entt::attribute{"pretty_name", "Script"},
78 })
79 .func<&component_meta<script_component>::exists>("component_exists"_hs)
80 .func<&component_meta<script_component>::add>("component_add"_hs)
81 .func<&component_meta<script_component>::save>("component_save"_hs)
82 .func<&component_meta<script_component>::load>("component_load"_hs)
83 .func<&component_meta<script_component>::remove>("component_remove"_hs);
84}
85
86template<typename Archive, typename T>
88{
89 template<typename Invoker>
90 static auto try_save_mono_invoker(ser20::detail::OutputArchiveBase& arbase,
91 const dotnet::object& obj,
92 const Invoker& invoker) -> bool
93 {
94 auto& ar = static_cast<Archive&>(arbase);
95 auto val = invoker.get_value(obj);
96 return try_save(ar, ser20::make_nvp(invoker.get_name(), val));
97 }
98
99 static auto try_save_mono_object(ser20::detail::OutputArchiveBase& arbase,
100 const dotnet::object& obj) -> bool
101 {
102 auto& ar = static_cast<Archive&>(arbase);
103 // Extract value directly from mono_object using mono_converter
104 T value = dotnet_converter<T>::from_managed(dotnet::get_managed_ptr(obj));
105 return try_save(ar, ser20::make_nvp("value", value));
106 }
107
108 static auto try_save_mono_field(ser20::detail::OutputArchiveBase& arbase,
109 const dotnet::object& obj,
110 const dotnet::field& field) -> bool
111 {
112 auto invoker = dotnet::make_field_invoker<T>(field);
113 return try_save_mono_invoker(arbase, obj, invoker);
114 }
115
116 static auto try_save_mono_property(ser20::detail::OutputArchiveBase& arbase,
117 const dotnet::object& obj,
118 const dotnet::property& prop) -> bool
119 {
120 auto invoker = dotnet::make_property_invoker<T>(prop);
121 return try_save_mono_invoker(arbase, obj, invoker);
122 }
123};
124
125template<typename Archive>
126struct mono_saver<Archive, entt::entity>
127{
128 template<typename Invoker>
129 static auto try_save_mono_invoker(ser20::detail::OutputArchiveBase& arbase,
130 const dotnet::object& obj,
131 const Invoker& invoker) -> bool
132 {
133 auto& ar = static_cast<Archive&>(arbase);
134 auto val = invoker.get_value(obj);
135
136
137 auto& save_ctx = get_save_context();
138 auto& registry = *save_ctx.save_source.registry();
139
141 e.handle = entt::const_handle(registry, val);
142
143 return try_save(ar, ser20::make_nvp(invoker.get_name(), e));
144 }
145
146 static auto try_save_mono_object(ser20::detail::OutputArchiveBase& arbase,
147 const dotnet::object& obj) -> bool
148 {
149 auto& ar = static_cast<Archive&>(arbase);
150
151 auto entity = dotnet_converter<entt::entity>::from_managed(dotnet::get_managed_ptr(obj));
152
153 auto& save_ctx = get_save_context();
154 auto& registry = *save_ctx.save_source.registry();
155
157 e.handle = entt::const_handle(registry, entity);
158
159 return try_save(ar, ser20::make_nvp("value", e));
160 }
161
162 static auto try_save_mono_field(ser20::detail::OutputArchiveBase& arbase,
163 const dotnet::object& obj,
164 const dotnet::field& field) -> bool
165 {
166 auto invoker = dotnet::make_field_invoker<entt::entity>(field);
167 return try_save_mono_invoker(arbase, obj, invoker);
168 }
169
170 static auto try_save_mono_property(ser20::detail::OutputArchiveBase& arbase,
171 const dotnet::object& obj,
172 const dotnet::property& prop) -> bool
173 {
174 auto invoker = dotnet::make_property_invoker<entt::entity>(prop);
175 return try_save_mono_invoker(arbase, obj, invoker);
176 }
177};
178
179template<typename Archive, typename T>
180struct mono_saver<Archive, asset_handle<T>>
181{
182 template<typename Invoker>
183 static auto try_save_mono_invoker(ser20::detail::OutputArchiveBase& arbase,
184 const dotnet::object& obj,
185 const Invoker& invoker) -> bool
186 {
187 auto& ar = static_cast<Archive&>(arbase);
188
189 auto val = invoker.get_value(obj);
190
191 asset_handle<T> asset{};
192 if(val)
193 {
194 const auto& invoker_type = invoker.get_type();
195 auto guid_property = invoker_type.get_property("uid");
196 auto mutable_uid_property = dotnet::make_property_invoker<hpp::uuid>(guid_property);
197 auto uid = mutable_uid_property.get_value(val);
198
199 auto& ctx = engine::context();
200 auto& am = ctx.get_cached<asset_manager>();
201 asset = am.get_asset<T>(uid);
202 }
203
204 return try_save(ar, ser20::make_nvp(invoker.get_name(), asset));
205 }
206
207 static auto try_save_mono_object(ser20::detail::OutputArchiveBase& arbase,
208 const dotnet::object& obj) -> bool
209 {
210 auto& ar = static_cast<Archive&>(arbase);
211
212 asset_handle<T> asset{};
213 if(obj.valid())
214 {
215 auto obj_type = obj.get_type();
216 auto prop = obj_type.get_property("uid");
217 if(prop.is_valid())
218 {
219 auto uid_prop = dotnet::make_property_invoker<hpp::uuid>(prop);
220 auto uid = uid_prop.get_value(obj);
221
222 auto& ctx = engine::context();
223 auto& am = ctx.get_cached<asset_manager>();
224 asset = am.get_asset<T>(uid);
225 }
226 }
227
228 return try_save(ar, ser20::make_nvp("value", asset));
229 }
230
231 static auto try_save_mono_field(ser20::detail::OutputArchiveBase& arbase,
232 const dotnet::object& obj,
233 const dotnet::field& field) -> bool
234 {
235 auto invoker = dotnet::make_field_invoker<dotnet::object>(field);
236 return try_save_mono_invoker(arbase, obj, invoker);
237 }
238
239 static auto try_save_mono_property(ser20::detail::OutputArchiveBase& arbase,
240 const dotnet::object& obj,
241 const dotnet::property& prop) -> bool
242 {
243 auto invoker = dotnet::make_property_invoker<dotnet::object>(prop);
244 return try_save_mono_invoker(arbase, obj, invoker);
245 }
246};
247
248template<typename Archive, typename T>
250{
251 template<typename U>
252 static auto is_supported_type(const dotnet::type& type) -> bool
253 {
254 return dotnet::is_compatible_type<U>(type);
255 }
256
257 template<typename Invoker>
258 static auto try_load_mono_invoker(ser20::detail::InputArchiveBase& arbase,
259 dotnet::object& obj,
260 const Invoker& invoker) -> bool
261 {
262 auto& ar = static_cast<Archive&>(arbase);
263
264 if(is_supported_type<T>(invoker.get_type()))
265 {
266 T val{};
267 if(try_load(ar, ser20::make_nvp(invoker.get_name(), val)))
268 {
269 invoker.set_value(obj, val);
270 }
271 return true;
272 }
273 return false;
274 }
275
276 static auto try_load_mono_object(ser20::detail::InputArchiveBase& arbase,
277 dotnet::object& obj) -> bool
278 {
279 auto& ar = static_cast<Archive&>(arbase);
280 T value{};
281 if(try_load(ar, ser20::make_nvp("value", value)))
282 {
283 // Box the value back into the mono_object
284 auto obj_type = obj.get_type();
285 if(obj_type.is_valuetype())
286 {
287 auto mono_value = dotnet_converter<T>::to_managed(value);
288 obj.box_value(mono_value, obj_type);
289 return true;
290 }
291 }
292 return false;
293 }
294
295 static auto try_load_mono_field(ser20::detail::InputArchiveBase& arbase,
296 dotnet::object& obj,
297 dotnet::field& field) -> bool
298 {
299 auto invoker = dotnet::make_field_invoker<T>(field);
300 return try_load_mono_invoker(arbase, obj, invoker);
301 }
302
303 static auto try_load_mono_property(ser20::detail::InputArchiveBase& arbase,
304 dotnet::object& obj,
305 dotnet::property& prop) -> bool
306 {
307 auto invoker = dotnet::make_property_invoker<T>(prop);
308 return try_load_mono_invoker(arbase, obj, invoker);
309 }
310};
311
312
313template<typename Archive>
314struct mono_loader<Archive, std::string>
315{
316 template<typename U>
317 static auto is_supported_type(const dotnet::type& type) -> bool
318 {
319 return type.is_string();
320 }
321
322 template<typename Invoker>
323 static auto try_load_mono_invoker(ser20::detail::InputArchiveBase& arbase,
324 dotnet::object& obj,
325 const Invoker& invoker) -> bool
326 {
327 auto& ar = static_cast<Archive&>(arbase);
328
329 if(is_supported_type<std::string>(invoker.get_type()))
330 {
331 std::string val{};
332 if(try_load(ar, ser20::make_nvp(invoker.get_name(), val)))
333 {
334 invoker.set_value(obj, val);
335 }
336 return true;
337 }
338 return false;
339 }
340
341 static auto try_load_mono_object(ser20::detail::InputArchiveBase& arbase,
342 dotnet::object& obj) -> bool
343 {
344 auto& ar = static_cast<Archive&>(arbase);
345 auto obj_type = obj.get_type();
347 {
348 std::string value{};
349 if(try_load(ar, ser20::make_nvp("value", value)))
350 {
351 obj = dotnet::object(dotnet_converter<std::string>::to_managed(value));
352 return true;
353 }
354 }
355
356
357 return false;
358 }
359
360 static auto try_load_mono_field(ser20::detail::InputArchiveBase& arbase,
361 dotnet::object& obj,
362 dotnet::field& field) -> bool
363 {
364 auto invoker = dotnet::make_field_invoker<std::string>(field);
365 return try_load_mono_invoker(arbase, obj, invoker);
366 }
367
368 static auto try_load_mono_property(ser20::detail::InputArchiveBase& arbase,
369 dotnet::object& obj,
370 dotnet::property& prop) -> bool
371 {
372 auto invoker = dotnet::make_property_invoker<std::string>(prop);
373 return try_load_mono_invoker(arbase, obj, invoker);
374 }
375};
376
377template<typename Archive>
378struct mono_loader<Archive, entt::entity>
379{
380 template<typename U>
381 static auto is_supported_type(const dotnet::type& type) -> bool
382 {
383 const auto& expected_name = type.get_name();
384 bool is_supported = std::is_same_v<entt::entity, U> && expected_name == "Entity";
385 return is_supported;
386 }
387
388 template<typename Invoker>
389 static auto try_load_mono_invoker(ser20::detail::InputArchiveBase& arbase,
390 dotnet::object& obj,
391 const Invoker& invoker) -> bool
392 {
393 auto& ar = static_cast<Archive&>(arbase);
394
395 if(is_supported_type<entt::entity>(invoker.get_type()))
396 {
398 if(try_load(ar, ser20::make_nvp(invoker.get_name(), val)))
399 {
400 invoker.set_value(obj, val.handle.entity());
401 }
402 else
403 {
404 return true;
405 }
406 return true;
407 }
408 return false;
409 }
410
411 static auto try_load_mono_object(ser20::detail::InputArchiveBase& arbase,
412 dotnet::object& obj) -> bool
413 {
414 auto& ar = static_cast<Archive&>(arbase);
416 if(try_load(ar, ser20::make_nvp("value", val)))
417 {
418 entt::entity entity = val.handle ? val.handle.entity() : entt::null;
419 // Box the entity value back into the mono_object
420 auto obj_type = obj.get_type();
421 if(obj_type.is_valuetype())
422 {
423 auto mono_entity = dotnet_converter<entt::entity>::to_managed(entity);
424 obj.box_value(mono_entity, obj_type);
425 return true;
426 }
427 }
428 return false;
429 }
430
431 static auto try_load_mono_field(ser20::detail::InputArchiveBase& arbase,
432 dotnet::object& obj,
433 dotnet::field& field) -> bool
434 {
435 auto invoker = dotnet::make_field_invoker<entt::entity>(field);
436 return try_load_mono_invoker(arbase, obj, invoker);
437 }
438
439 static auto try_load_mono_property(ser20::detail::InputArchiveBase& arbase,
440 dotnet::object& obj,
441 dotnet::property& prop) -> bool
442 {
443 auto invoker = dotnet::make_property_invoker<entt::entity>(prop);
444 return try_load_mono_invoker(arbase, obj, invoker);
445 }
446};
447
448template<typename Archive, typename T>
449struct mono_loader<Archive, asset_handle<T>>
450{
451 template<typename U>
452 static auto is_supported_type(const dotnet::type& type) -> bool
453 {
454 const auto& expected_name = type.get_name();
455 bool is_supported = false;
456
457 if(!is_supported)
458 {
459 is_supported |= std::is_same_v<asset_handle<gfx::texture>, U> && expected_name == "Texture";
460 }
461 if(!is_supported)
462 {
463 is_supported |= std::is_same_v<asset_handle<material>, U> && expected_name == "Material";
464 }
465 if(!is_supported)
466 {
467 is_supported |= std::is_same_v<asset_handle<mesh>, U> && expected_name == "Mesh";
468 }
469 if(!is_supported)
470 {
471 is_supported |= std::is_same_v<asset_handle<animation_clip>, U> && expected_name == "AnimationClip";
472 }
473 if(!is_supported)
474 {
475 is_supported |= std::is_same_v<asset_handle<prefab>, U> && expected_name == "Prefab";
476 }
477 if(!is_supported)
478 {
479 is_supported |= std::is_same_v<asset_handle<scene_prefab>, U> && expected_name == "Scene";
480 }
481 if(!is_supported)
482 {
483 is_supported |= std::is_same_v<asset_handle<physics_material>, U> && expected_name == "PhysicsMaterial";
484 }
485 if(!is_supported)
486 {
487 is_supported |= std::is_same_v<asset_handle<audio_clip>, U> && expected_name == "AudioClip";
488 }
489 if(!is_supported)
490 {
491 is_supported |= std::is_same_v<asset_handle<font>, U> && expected_name == "Font";
492 }
493 return is_supported;
494 }
495
496 template<typename Invoker>
497 static auto try_load_mono_invoker(ser20::detail::InputArchiveBase& arbase,
498 dotnet::object& obj,
499 const Invoker& invoker) -> bool
500 {
501 auto& ar = static_cast<Archive&>(arbase);
502
503 if(is_supported_type<asset_handle<T>>(invoker.get_type()))
504 {
505 asset_handle<T> val{};
506 if(try_load(ar, ser20::make_nvp(invoker.get_name(), val)))
507 {
508 const auto& field_type = invoker.get_type();
509 auto guid_property = field_type.get_property("uid");
510 auto mutable_uid_property = dotnet::make_property_invoker<hpp::uuid>(guid_property);
511
512 auto var = invoker.get_value(obj);
513 if(!var && val)
514 {
515 var = field_type.new_instance();
516 invoker.set_value(obj, var);
517 }
518
519 if(var)
520 {
521 mutable_uid_property.set_value(var, val.uid());
522 }
523 return true;
524 }
525 }
526 return false;
527 }
528
529 static auto try_load_mono_object(ser20::detail::InputArchiveBase& arbase,
530 dotnet::object& obj) -> bool
531 {
532 auto& ar = static_cast<Archive&>(arbase);
533 const auto& obj_type = obj.get_type();
534
536 {
537 asset_handle<T> asset{};
538 if(try_load(ar, ser20::make_nvp("value", asset)))
539 {
540 if(obj.valid())
541 {
542 auto prop = obj_type.get_property("uid");
543 if(prop.is_valid())
544 {
545 auto uid_prop = dotnet::make_property_invoker<hpp::uuid>(prop);
546 uid_prop.set_value(obj, asset ? asset.uid() : hpp::uuid{});
547 return true;
548 }
549
550 }
551 return true;
552 }
553 }
554 return false;
555 }
556
557 static auto try_load_mono_field(ser20::detail::InputArchiveBase& arbase,
558 dotnet::object& obj,
559 dotnet::field& field) -> bool
560 {
561 auto invoker = dotnet::make_field_invoker<dotnet::object>(field);
562 return try_load_mono_invoker(arbase, obj, invoker);
563 }
564
565 static auto try_load_mono_property(ser20::detail::InputArchiveBase& arbase,
566 dotnet::object& obj,
567 dotnet::property& prop) -> bool
568 {
569 auto invoker = dotnet::make_property_invoker<dotnet::object>(prop);
570 return try_load_mono_invoker(arbase, obj, invoker);
571 }
572};
573} // namespace unravel
574
575
576auto get_type_by_fullname(const std::string& fullname) -> dotnet::type
577{
578 auto& ctx = unravel::engine::context();
579 auto& script_sys = ctx.get_cached<unravel::script_system>();
580 return script_sys.get_type_by_fullname(fullname);
581}
582
583namespace ser20
584{
585template<typename Archive>
586inline auto try_save_mono_type(Archive& ar, const char* name, const dotnet::type& t) -> bool
587{
588 return try_save(ar, ser20::make_nvp(name, t.get_fullname()));
589}
590
591template<typename Archive>
592inline auto try_load_mono_type(Archive& ar, const char* name, dotnet::type& t) -> bool
593{
594 std::string fullname;
595 if(try_load(ar, ser20::make_nvp(name, fullname)))
596 {
597 t = get_type_by_fullname(fullname);
598 return true;
599 }
600 return false;
601}
602
603template<typename Archive, typename T>
604inline void SAVE_FUNCTION_NAME(Archive& ar, const dotnet::vector_like_wrapper<T>& obj)
605{
606 try_save_mono_type(ar, "type", obj.type);
607
608 try_save(ar, ser20::make_nvp("size", obj.container.size()));
609 try_save(ar, ser20::make_nvp("container", obj.container));
610}
611
612template<typename Archive, typename T>
613inline void LOAD_FUNCTION_NAME(Archive& ar, dotnet::vector_like_wrapper<T>& obj)
614{
615 try_load_mono_type(ar, "type", obj.type);
616
617 if(!obj.type.valid())
618 {
619 return;
620 }
621
622 ser20::size_type size{};
623 try_load(ar, ser20::make_nvp("size", size));
624 obj.container.resize(static_cast<std::size_t>(size));
625 std::vector<dotnet::object_pinned_ptr> element_pins;
626 element_pins.reserve(obj.container.size());
627 for(auto& v : obj.container)
628 {
629 v = obj.type.new_instance();
630 element_pins.push_back(dotnet::make_object_pinned(v));
631 }
632 {
634 try_load(ar, ser20::make_nvp("container", obj.container));
635 }
636}
637
638SAVE(dotnet::object)
639{
640 using namespace unravel;
641 auto pinned_obj = dotnet::make_object_pinned(obj);
642 // First, try object-level serializer (like inspector's get_object_inspector)
643 using mono_object_serializer = std::function<bool(ser20::detail::OutputArchiveBase&, const dotnet::object&)>;
644
645 auto get_object_serializer = [](const std::string& type_name) -> const mono_object_serializer&
646 {
647 // clang-format off
648 static std::map<std::string, mono_object_serializer> reg = {
669 {"Texture", &mono_saver<Archive, asset_handle<gfx::texture>>::try_save_mono_object},
670 {"Material", &mono_saver<Archive, asset_handle<material>>::try_save_mono_object},
671 {"Mesh", &mono_saver<Archive, asset_handle<mesh>>::try_save_mono_object},
672 {"AnimationClip", &mono_saver<Archive, asset_handle<animation_clip>>::try_save_mono_object},
673 {"Prefab", &mono_saver<Archive, asset_handle<prefab>>::try_save_mono_object},
674 {"Scene", &mono_saver<Archive, asset_handle<scene_prefab>>::try_save_mono_object},
675 {"PhysicsMaterial", &mono_saver<Archive, asset_handle<physics_material>>::try_save_mono_object},
676 {"AudioClip", &mono_saver<Archive, asset_handle<audio_clip>>::try_save_mono_object},
677 {"Font", &mono_saver<Archive, asset_handle<font>>::try_save_mono_object},
678 };
679 // clang-format on
680
681 auto it = reg.find(type_name);
682 if(it != reg.end())
683 {
684 return it->second;
685 }
686 static const mono_object_serializer empty;
687 return empty;
688 };
689
690 const auto& type = obj.get_type();
691 if(!type.valid())
692 {
693 return;
694 }
695 auto object_serializer = get_object_serializer(type.get_name());
696 if(object_serializer)
697 {
698 object_serializer(ar, obj);
699 return;
700 }
701
702 auto& ctx = engine::context();
703 auto& script_sys = ctx.get_cached<script_system>();
704 bool should_serialize = (type.is_serializable() || type.is_derived_from(script_sys.get_scriptable_component_base_type()));
705
706 if(!should_serialize)
707 {
708 return;
709 }
710
711
712 if(!obj.valid())
713 {
714 return;
715 }
716
717 // If no object serializer found, iterate through fields/properties (like inspector)
718 using mono_field_serializer =
719 std::function<bool(ser20::detail::OutputArchiveBase&, const dotnet::object&, const dotnet::field&)>;
720
721 auto get_field_serilizer = [](const std::string& type_name) -> const mono_field_serializer&
722 {
723 // clang-format off
724 static std::map<std::string, mono_field_serializer> reg = {
745 {"Texture", &mono_saver<Archive, asset_handle<gfx::texture>>::try_save_mono_field},
746 {"Material", &mono_saver<Archive, asset_handle<material>>::try_save_mono_field},
747 {"Mesh", &mono_saver<Archive, asset_handle<mesh>>::try_save_mono_field},
748 {"AnimationClip", &mono_saver<Archive, asset_handle<animation_clip>>::try_save_mono_field},
749 {"Prefab", &mono_saver<Archive, asset_handle<prefab>>::try_save_mono_field},
750 {"Scene", &mono_saver<Archive, asset_handle<scene_prefab>>::try_save_mono_field},
751 {"PhysicsMaterial", &mono_saver<Archive, asset_handle<physics_material>>::try_save_mono_field},
752 {"AudioClip", &mono_saver<Archive, asset_handle<audio_clip>>::try_save_mono_field},
753 {"Font", &mono_saver<Archive, asset_handle<font>>::try_save_mono_field},
754 };
755 // clang-format on
756
757 auto it = reg.find(type_name);
758 if(it != reg.end())
759 {
760 return it->second;
761 }
762 static const mono_field_serializer empty;
763 return empty;
764 };
765
766 using mono_property_serializer =
767 std::function<bool(ser20::detail::OutputArchiveBase&, const dotnet::object&, const dotnet::property&)>;
768
769 auto get_property_serilizer = [](const std::string& type_name) -> const mono_property_serializer&
770 {
771 // clang-format off
772 static std::map<std::string, mono_property_serializer> reg = {
793 {"Texture", &mono_saver<Archive, asset_handle<gfx::texture>>::try_save_mono_property},
794 {"Material", &mono_saver<Archive, asset_handle<material>>::try_save_mono_property},
795 {"Mesh", &mono_saver<Archive, asset_handle<mesh>>::try_save_mono_property},
796 {"AnimationClip", &mono_saver<Archive, asset_handle<animation_clip>>::try_save_mono_property},
797 {"Prefab", &mono_saver<Archive, asset_handle<prefab>>::try_save_mono_property},
798 {"Scene", &mono_saver<Archive, asset_handle<scene_prefab>>::try_save_mono_property},
799 {"PhysicsMaterial", &mono_saver<Archive, asset_handle<physics_material>>::try_save_mono_property},
800 {"AudioClip", &mono_saver<Archive, asset_handle<audio_clip>>::try_save_mono_property},
801 {"Font", &mono_saver<Archive, asset_handle<font>>::try_save_mono_property},
802 };
803 // clang-format on
804
805 auto it = reg.find(type_name);
806 if(it != reg.end())
807 {
808 return it->second;
809 }
810 static const mono_property_serializer empty;
811 return empty;
812 };
813
814 bool include_base = true;
815 auto fields = type.get_fields(include_base);
816 auto properties = type.get_properties(include_base);
817
818 for(auto& field : fields)
819 {
820 if(field.get_visibility() == dotnet::visibility::vis_public)
821 {
822 if(field.is_static() || field.has_attribute("HideAttribute"))
823 {
824 continue;
825 }
826
827 const auto& field_type = field.get_type();
828 auto field_serilizer = get_field_serilizer(field_type.get_name());
829 if(field_serilizer)
830 {
831 field_serilizer(ar, obj, field);
832 }
833 else if(field_type.is_enum())
834 {
835 auto enum_type = field_type.get_enum_base_type();
836 auto enum_serilizer = get_field_serilizer(enum_type.get_name());
837 if(enum_serilizer)
838 {
839 enum_serilizer(ar, obj, field);
840 }
841 }
842 else if(field_type.is_array() || field_type.is_list())
843 {
844 // Handle arrays and lists - convert to vector and serialize
845 auto invoker = dotnet::make_field_invoker<dotnet::object>(field);
846 auto collection_obj = invoker.get_value(obj);
847 if(collection_obj.valid())
848 {
849 serialization::path_segment_guard guard(field.get_name());
850 if(field_type.is_array())
851 {
852 dotnet::array<dotnet::object> array(collection_obj);
853 auto pinned_array = dotnet::make_array_pinned(array);
854 auto vec = array.to_vector_wrapper<std::vector<dotnet::object>>();
855 auto element_pins = dotnet::pin_vector_elements(vec.container);
856 try_save(ar, ser20::make_nvp(field.get_name(), vec));
857 }
858 else if(field_type.is_list())
859 {
860 dotnet::list<dotnet::object> list(collection_obj);
861 auto pinned_list = dotnet::make_list_pinned(list);
862 auto vec = list.to_vector_wrapper<std::vector<dotnet::object>>();
863 auto element_pins = dotnet::pin_vector_elements(vec.container);
864 try_save(ar, ser20::make_nvp(field.get_name(), vec));
865 }
866 }
867 }
868 else if(field_type.is_serializable())
869 {
870 // Recursively handle serializable nested objects
871 auto invoker = dotnet::make_field_invoker<dotnet::object>(field);
872 auto nested_obj = invoker.get_value(obj);
873 if(nested_obj.valid())
874 {
875 auto pinned_nested = dotnet::make_object_pinned(nested_obj);
876 serialization::path_segment_guard guard(field.get_name());
877 try_save(ar, ser20::make_nvp(field.get_name(), nested_obj));
878 }
879 }
880 }
881 }
882
883 for(auto& prop : properties)
884 {
885 if(prop.get_visibility() == dotnet::visibility::vis_public)
886 {
887 if(prop.is_static() || prop.has_attribute("HideAttribute"))
888 {
889 continue;
890 }
891 const auto& prop_type = prop.get_type();
892
893 auto prop_serilizer = get_property_serilizer(prop_type.get_name());
894 if(prop_serilizer)
895 {
896 prop_serilizer(ar, obj, prop);
897 }
898 else if(prop_type.is_enum())
899 {
900 auto enum_type = prop_type.get_enum_base_type();
901 auto enum_serilizer = get_property_serilizer(enum_type.get_name());
902 if(enum_serilizer)
903 {
904 enum_serilizer(ar, obj, prop);
905 }
906 }
907 else if(prop_type.is_array() || prop_type.is_list())
908 {
909 // Handle arrays and lists - convert to vector and serialize
910 auto invoker = dotnet::make_property_invoker<dotnet::object>(prop);
911 auto collection_obj = invoker.get_value(obj);
912 if(collection_obj.valid())
913 {
914 serialization::path_segment_guard guard(prop.get_name());
915 if(prop_type.is_array())
916 {
917 dotnet::array<dotnet::object> array(collection_obj);
918 auto pinned_array = dotnet::make_array_pinned(array);
919 auto vec = array.to_vector_wrapper<std::vector<dotnet::object>>();
920 auto element_pins = dotnet::pin_vector_elements(vec.container);
921 try_save(ar, ser20::make_nvp(prop.get_name(), vec));
922 }
923 else if(prop_type.is_list())
924 {
925 dotnet::list<dotnet::object> list(collection_obj);
926 auto pinned_list = dotnet::make_list_pinned(list);
927 auto vec = list.to_vector_wrapper<std::vector<dotnet::object>>();
928 auto element_pins = dotnet::pin_vector_elements(vec.container);
929 try_save(ar, ser20::make_nvp(prop.get_name(), vec));
930 }
931 }
932 }
933 else if(prop_type.is_serializable())
934 {
935 // Recursively handle serializable nested objects
936 auto invoker = dotnet::make_property_invoker<dotnet::object>(prop);
937 auto nested_obj = invoker.get_value(obj);
938 if(nested_obj.valid())
939 {
940 auto pinned_nested = dotnet::make_object_pinned(nested_obj);
941 serialization::path_segment_guard guard(prop.get_name());
942 try_save(ar, ser20::make_nvp(prop.get_name(), nested_obj));
943 }
944 }
945 }
946 }
947
948}
949
950LOAD(dotnet::object)
951{
952 using namespace unravel;
953
954 auto pinned_obj = dotnet::make_object_pinned(obj);
955 // First, try object-level serializer (like inspector's get_object_inspector)
956 using mono_object_serializer = std::function<bool(ser20::detail::InputArchiveBase&, dotnet::object&)>;
957
958 auto get_object_serializer = [](const std::string& type_name) -> const mono_object_serializer&
959 {
960 // clang-format off
961 static const std::map<std::string, mono_object_serializer> reg = {
982 {"Texture", &mono_loader<Archive, asset_handle<gfx::texture>>::try_load_mono_object},
983 {"Material", &mono_loader<Archive, asset_handle<material>>::try_load_mono_object},
984 {"Mesh", &mono_loader<Archive, asset_handle<mesh>>::try_load_mono_object},
985 {"AnimationClip", &mono_loader<Archive, asset_handle<animation_clip>>::try_load_mono_object},
986 {"Prefab", &mono_loader<Archive, asset_handle<prefab>>::try_load_mono_object},
987 {"Scene", &mono_loader<Archive, asset_handle<scene_prefab>>::try_load_mono_object},
988 {"PhysicsMaterial", &mono_loader<Archive, asset_handle<physics_material>>::try_load_mono_object},
989 {"AudioClip", &mono_loader<Archive, asset_handle<audio_clip>>::try_load_mono_object},
990 {"Font", &mono_loader<Archive, asset_handle<font>>::try_load_mono_object},
991 };
992 // clang-format on
993
994 auto it = reg.find(type_name);
995 if(it != reg.end())
996 {
997 return it->second;
998 }
999 static const mono_object_serializer empty;
1000 return empty;
1001 };
1002
1003 const auto& type = obj.get_type();
1004
1005
1006 if(!type.valid())
1007 {
1008 return;
1009 }
1010 auto object_serializer = get_object_serializer(type.get_name());
1011 if(object_serializer)
1012 {
1013 object_serializer(ar, obj);
1014 return;
1015 }
1016
1017
1018 auto& ctx = engine::context();
1019 auto& script_sys = ctx.get_cached<script_system>();
1020 bool should_serialize = (type.is_serializable() || type.is_derived_from(script_sys.get_scriptable_component_base_type()));
1021
1022 if(!should_serialize)
1023 {
1024 return;
1025 }
1026
1027 // If no object serializer found, iterate through fields/properties (like inspector)
1028 using mono_field_serializer =
1029 std::function<bool(ser20::detail::InputArchiveBase&, dotnet::object&, dotnet::field&)>;
1030
1031 auto get_field_serilizer = [](const std::string& type_name) -> const mono_field_serializer&
1032 {
1033 // clang-format off
1034 static const std::map<std::string, mono_field_serializer> reg = {
1055 {"Texture", &mono_loader<Archive, asset_handle<gfx::texture>>::try_load_mono_field},
1056 {"Material", &mono_loader<Archive, asset_handle<material>>::try_load_mono_field},
1057 {"Mesh", &mono_loader<Archive, asset_handle<mesh>>::try_load_mono_field},
1058 {"AnimationClip", &mono_loader<Archive, asset_handle<animation_clip>>::try_load_mono_field},
1059 {"Prefab", &mono_loader<Archive, asset_handle<prefab>>::try_load_mono_field},
1060 {"Scene", &mono_loader<Archive, asset_handle<scene_prefab>>::try_load_mono_field},
1061 {"PhysicsMaterial", &mono_loader<Archive, asset_handle<physics_material>>::try_load_mono_field},
1062 {"AudioClip", &mono_loader<Archive, asset_handle<audio_clip>>::try_load_mono_field},
1063 {"Font", &mono_loader<Archive, asset_handle<font>>::try_load_mono_field},
1064 };
1065 // clang-format on
1066
1067 auto it = reg.find(type_name);
1068 if(it != reg.end())
1069 {
1070 return it->second;
1071 }
1072 static const mono_field_serializer empty;
1073 return empty;
1074 };
1075
1076 using mono_property_serializer =
1077 std::function<bool(ser20::detail::InputArchiveBase&, dotnet::object&, dotnet::property&)>;
1078
1079 auto get_property_serilizer = [](const std::string& type_name) -> const mono_property_serializer&
1080 {
1081 // clang-format off
1082 static const std::map<std::string, mono_property_serializer> reg = {
1103 {"Texture", &mono_loader<Archive, asset_handle<gfx::texture>>::try_load_mono_property},
1104 {"Material", &mono_loader<Archive, asset_handle<material>>::try_load_mono_property},
1105 {"Mesh", &mono_loader<Archive, asset_handle<mesh>>::try_load_mono_property},
1106 {"AnimationClip", &mono_loader<Archive, asset_handle<animation_clip>>::try_load_mono_property},
1107 {"Prefab", &mono_loader<Archive, asset_handle<prefab>>::try_load_mono_property},
1108 {"Scene", &mono_loader<Archive, asset_handle<scene_prefab>>::try_load_mono_property},
1109 {"PhysicsMaterial", &mono_loader<Archive, asset_handle<physics_material>>::try_load_mono_property},
1110 {"AudioClip", &mono_loader<Archive, asset_handle<audio_clip>>::try_load_mono_property},
1111 {"Font", &mono_loader<Archive, asset_handle<font>>::try_load_mono_property},
1112 };
1113 // clang-format on
1114
1115 auto it = reg.find(type_name);
1116 if(it != reg.end())
1117 {
1118 return it->second;
1119 }
1120 static const mono_property_serializer empty;
1121 return empty;
1122 };
1123
1124 bool include_base = true;
1125 auto fields = type.get_fields(include_base);
1126 auto properties = type.get_properties(include_base);
1127
1128 for(auto& field : fields)
1129 {
1130 if(field.get_visibility() == dotnet::visibility::vis_public)
1131 {
1132 const auto& field_type = field.get_type();
1133 auto field_serilizer = get_field_serilizer(field_type.get_name());
1134 if(field_serilizer)
1135 {
1136 field_serilizer(ar, obj, field);
1137 }
1138 else if(field_type.is_enum())
1139 {
1140 auto enum_type = field_type.get_enum_base_type();
1141 auto enum_serilizer = get_field_serilizer(enum_type.get_name());
1142 if(enum_serilizer)
1143 {
1144 enum_serilizer(ar, obj, field);
1145 }
1146 }
1147 else if(field_type.is_array() || field_type.is_list())
1148 {
1149 // Handle arrays and lists - load vector and convert back
1150 auto invoker = dotnet::make_field_invoker<dotnet::object>(field);
1151 dotnet::vector_like_wrapper<std::vector<dotnet::object>> vec;
1152 if(try_load(ar, ser20::make_nvp(field.get_name(), vec)))
1153 {
1154 auto element_pins = dotnet::pin_vector_elements(vec.container);
1155 if(field_type.is_array())
1156 {
1157 dotnet::array<dotnet::object> array(vec.container, vec.type);
1158 invoker.set_value(obj, array);
1159 }
1160 else if(field_type.is_list())
1161 {
1162 dotnet::list<dotnet::object> list(vec.container, vec.type);
1163 invoker.set_value(obj, list);
1164 }
1165 }
1166 }
1167 else if(field_type.is_serializable())
1168 {
1169 // Recursively handle serializable nested objects
1170 auto invoker = dotnet::make_field_invoker<dotnet::object>(field);
1171 auto nested_obj = invoker.get_value(obj);
1172 if(nested_obj.valid())
1173 {
1174 auto pinned_nested = dotnet::make_object_pinned(nested_obj);
1175 try_load(ar, ser20::make_nvp(field.get_name(), nested_obj));
1176 invoker.set_value(obj, nested_obj);
1177 }
1178 }
1179 }
1180 }
1181
1182 for(auto& prop : properties)
1183 {
1184 if(prop.get_visibility() == dotnet::visibility::vis_public)
1185 {
1186 const auto& prop_type = prop.get_type();
1187 auto prop_serilizer = get_property_serilizer(prop_type.get_name());
1188 if(prop_serilizer)
1189 {
1190 prop_serilizer(ar, obj, prop);
1191 }
1192 else if(prop_type.is_enum())
1193 {
1194 auto enum_type = prop_type.get_enum_base_type();
1195 auto enum_serilizer = get_property_serilizer(enum_type.get_name());
1196 if(enum_serilizer)
1197 {
1198 enum_serilizer(ar, obj, prop);
1199 }
1200 }
1201 else if(prop_type.is_array() || prop_type.is_list())
1202 {
1203 // Handle arrays and lists - load vector and convert back
1204 auto invoker = dotnet::make_property_invoker<dotnet::object>(prop);
1205 dotnet::vector_like_wrapper<std::vector<dotnet::object>> vec;
1206 if(try_load(ar, ser20::make_nvp(prop.get_name(), vec)))
1207 {
1208 auto element_pins = dotnet::pin_vector_elements(vec.container);
1209 if(prop_type.is_array())
1210 {
1211 dotnet::array<dotnet::object> array(vec.container, vec.type);
1212 invoker.set_value(obj, array);
1213 }
1214 else if(prop_type.is_list())
1215 {
1216 dotnet::list<dotnet::object> list(vec.container, vec.type);
1217 invoker.set_value(obj, list);
1218 }
1219 }
1220 }
1221 else if(prop_type.is_serializable())
1222 {
1223 // Recursively handle serializable nested objects
1224 auto invoker = dotnet::make_property_invoker<dotnet::object>(prop);
1225 auto nested_obj = invoker.get_value(obj);
1226 if(nested_obj.valid())
1227 {
1228 auto pinned_nested = dotnet::make_object_pinned(nested_obj);
1229 try_load(ar, ser20::make_nvp(prop.get_name(), nested_obj));
1230 invoker.set_value(obj, nested_obj);
1231 }
1232 }
1233 }
1234 }
1235
1236}
1239}
1240
1241
1242
1243
1244
1245namespace unravel
1246{
1248{
1249 auto object = obj.pinned->get_object();
1250 const auto& type = object.get_type();
1251
1252 try_save_mono_type(ar, "type", type);
1253
1254 SAVE_FUNCTION_NAME(ar, object);
1255}
1258
1260{
1261 dotnet::type script_type;
1262 try_load_mono_type(ar, "type", script_type);
1263
1264
1265 if(!script_type.valid())
1266 {
1267 return;
1268 }
1269
1270
1271 serialization::path_segment_guard guard(script_type.get_fullname());
1272
1273 auto* existing_obj = script_component_loader_ctx
1274 ? script_component_loader_ctx->get_script_object(script_type.get_hash())
1275 : nullptr;
1276 if(existing_obj)
1277 {
1278 obj = *existing_obj;
1279 }
1280 else
1281 {
1282 auto object = script_type.new_instance();
1283 obj = script_component::script_object(object);
1284 }
1285
1286 auto pinned_object = obj.pinned->get_object();
1287
1288 LOAD_FUNCTION_NAME(ar, pinned_object);
1289
1290}
1293
1295{
1296 const auto& comps = obj.get_script_components();
1297 try_save(ar, ser20::make_nvp("script_components", comps));
1298}
1301
1303{
1304
1305
1306 auto& load_ctx = get_load_context();
1307 if(load_ctx.is_updating_prefab())
1308 {
1309
1310 script_component_loader_context ctx;
1311 ctx.script_objects = obj.get_script_components();
1312
1313 script_component_loader_ctx = &ctx;
1314
1315 auto _ = hpp::finally([&]()
1316 {
1317 script_component_loader_ctx = nullptr;
1318 });
1319
1320
1322 if(try_load(ar, ser20::make_nvp("script_components", comps)))
1323 {
1324 obj.add_missing_script_components(comps);
1325 }
1326
1327 }
1328 else
1329 {
1331 if(try_load(ar, ser20::make_nvp("script_components", comps)))
1332 {
1333 obj.add_script_components(comps);
1334 }
1335 }
1336
1337}
1340
1341
1342
1343
1344auto save_to_stream(std::ostream& stream, entt::const_handle e, const script_component::script_object& obj) -> bool
1345{
1346 bool pushed = push_save_context();
1347 auto& save_ctx = get_save_context();
1348 save_ctx.save_source = e;
1349 save_ctx.to_prefab = false;
1350
1351 bool was_successful = false;
1352 try
1353 {
1355 ar(ser20::make_nvp("script_object", obj));
1356 was_successful = true;
1357 }
1358 catch(const std::exception& e)
1359 {
1360 APPLOG_ERROR("Failed to save script component to stream: {}", e.what());
1361 }
1362
1363 save_ctx.to_prefab = false;
1364 save_ctx.save_source = {};
1365 pop_save_context(pushed);
1366 return was_successful;
1367}
1368auto load_from_stream(std::istream& stream, entt::handle e, script_component::script_object& obj) -> bool
1369{
1370 bool pushed = push_load_context(*e.registry());
1371
1372 bool was_successful = false;
1373 try
1374 {
1376 ar(ser20::make_nvp("script_object", obj));
1377 was_successful = true;
1378 }
1379 catch(const std::exception& e)
1380 {
1381 APPLOG_ERROR("Failed to load script component from stream: {}", e.what());
1382 }
1383 pop_load_context(pushed);
1384 return was_successful;
1385}
1386
1387} // namespace unravel
1388
Manages assets, including loading, unloading, and storage.
auto get_asset(const std::string &key, load_flags flags=load_flags::standard, load_mode mode=load_mode::immediate) -> asset_handle< T >
Gets an asset by its key.
Class that contains core data for audio listeners. There can only be one instance of it per scene.
std::vector< script_object > script_components_t
std::string name
Definition hub.cpp:33
#define APPLOG_ERROR(...)
Definition logging.h:20
texture_job_type type
auto get_type_by_fullname(const std::string &fullname) -> dotnet::type
std::vector< script_component::script_object > script_objects
attributes::value_type attribute
Definition reflection.h:19
std::map< std::string, meta_any > attributes
Definition reflection.h:18
Definition yaml.hpp:46
auto try_load_mono_type(Archive &ar, const char *name, dotnet::type &t) -> bool
BinaryInputArchive iarchive_binary_t
simd::JSONOutputArchive oarchive_associative_t
BinaryOutputArchive oarchive_binary_t
simd::JSONInputArchive iarchive_associative_t
auto try_save_mono_type(Archive &ar, const char *name, const dotnet::type &t) -> bool
Hash specialization for batch_key to enable use in std::unordered_map.
auto push_save_context() -> bool
Definition entity.cpp:118
void pop_load_context(bool push_result)
Definition entity.cpp:69
auto push_load_context(entt::registry &registry) -> bool
Definition entity.cpp:58
auto get_save_context() -> save_context &
Definition entity.cpp:139
auto get_load_context() -> load_context &
Definition entity.cpp:90
auto load_from_stream(std::istream &stream, entt::handle e, script_component::script_object &obj) -> bool
void pop_save_context(bool push_result)
Definition entity.cpp:129
auto save_to_stream(std::ostream &stream, entt::const_handle e, const script_component::script_object &obj) -> bool
#define REFLECT(cls)
Definition reflection.h:149
entt::handle entity
#define SAVE_INSTANTIATE(cls, Archive)
#define LOAD(cls)
auto try_save(Archive &ar, ser20::NameValuePair< T > &&t, const hpp::source_location &loc=hpp::source_location::current()) -> bool
#define LOAD_INSTANTIATE(cls, Archive)
#define LOAD_FUNCTION_NAME
#define SAVE(cls)
auto try_load(Archive &ar, ser20::NameValuePair< T > &&t, const hpp::source_location &loc=hpp::source_location::current()) -> bool
#define SAVE_FUNCTION_NAME
Thread-safe handle to an asset.
static auto context() -> rtti::context &
Definition engine.cpp:111
static auto try_load_mono_object(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj) -> bool
static auto try_load_mono_field(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj, dotnet::field &field) -> bool
static auto is_supported_type(const dotnet::type &type) -> bool
static auto try_load_mono_property(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj, dotnet::property &prop) -> bool
static auto try_load_mono_invoker(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj, const Invoker &invoker) -> bool
static auto try_load_mono_object(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj) -> bool
static auto try_load_mono_invoker(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj, const Invoker &invoker) -> bool
static auto try_load_mono_field(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj, dotnet::field &field) -> bool
static auto is_supported_type(const dotnet::type &type) -> bool
static auto try_load_mono_property(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj, dotnet::property &prop) -> bool
static auto try_load_mono_property(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj, dotnet::property &prop) -> bool
static auto try_load_mono_field(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj, dotnet::field &field) -> bool
static auto try_load_mono_object(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj) -> bool
static auto try_load_mono_invoker(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj, const Invoker &invoker) -> bool
static auto is_supported_type(const dotnet::type &type) -> bool
static auto try_load_mono_field(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj, dotnet::field &field) -> bool
static auto try_load_mono_property(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj, dotnet::property &prop) -> bool
static auto try_load_mono_invoker(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj, const Invoker &invoker) -> bool
static auto is_supported_type(const dotnet::type &type) -> bool
static auto try_load_mono_object(ser20::detail::InputArchiveBase &arbase, dotnet::object &obj) -> bool
static auto try_save_mono_property(ser20::detail::OutputArchiveBase &arbase, const dotnet::object &obj, const dotnet::property &prop) -> bool
static auto try_save_mono_field(ser20::detail::OutputArchiveBase &arbase, const dotnet::object &obj, const dotnet::field &field) -> bool
static auto try_save_mono_object(ser20::detail::OutputArchiveBase &arbase, const dotnet::object &obj) -> bool
static auto try_save_mono_invoker(ser20::detail::OutputArchiveBase &arbase, const dotnet::object &obj, const Invoker &invoker) -> bool
static auto try_save_mono_object(ser20::detail::OutputArchiveBase &arbase, const dotnet::object &obj) -> bool
static auto try_save_mono_invoker(ser20::detail::OutputArchiveBase &arbase, const dotnet::object &obj, const Invoker &invoker) -> bool
static auto try_save_mono_field(ser20::detail::OutputArchiveBase &arbase, const dotnet::object &obj, const dotnet::field &field) -> bool
static auto try_save_mono_property(ser20::detail::OutputArchiveBase &arbase, const dotnet::object &obj, const dotnet::property &prop) -> bool
static auto try_save_mono_field(ser20::detail::OutputArchiveBase &arbase, const dotnet::object &obj, const dotnet::field &field) -> bool
static auto try_save_mono_invoker(ser20::detail::OutputArchiveBase &arbase, const dotnet::object &obj, const Invoker &invoker) -> bool
static auto try_save_mono_object(ser20::detail::OutputArchiveBase &arbase, const dotnet::object &obj) -> bool
static auto try_save_mono_property(ser20::detail::OutputArchiveBase &arbase, const dotnet::object &obj, const dotnet::property &prop) -> bool
auto get_type_by_fullname(const std::string &fullname) const -> dotnet::type