Unravel Engine C++ Reference
Loading...
Searching...
No Matches
editing_manager.cpp
Go to the documentation of this file.
1#include "editing_manager.h"
3#include "imgui/imgui.h"
4#include "logging/logging.h"
6#include <chrono>
11
12#include <engine/ecs/ecs.h>
14#include <engine/engine.h>
15#include <engine/events.h>
18
20#include <editor/events.h>
23#include <imgui_widgets/gizmo.h>
26
27#include <filedialog/filedialog.h>
28
29namespace unravel
30{
31
32namespace
33{
34 struct merge_session
35 {
36 uint64_t epoch = 1; // increments on boundaries (press/release/focus loss)
37 bool down_prev = false;
38
39 auto is_active() const -> bool
40 {
41 return ImGui::IsMouseDown(ImGuiMouseButton_Left) || ImGui::IsAnyItemActive();
42 }
43
44 void tick()
45 {
46 const bool down = is_active();
47
48
49 if(!ImGui::IsAnyItemActive())
50 {
51 // Bump the epoch on any boundary so new actions won't merge with the previous batch.
52 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
53 ImGui::GetIO().AppFocusLost)
54 {
55 ++epoch;
56 }
57
58 if(ImGui::IsMouseReleased(ImGuiMouseButton_Left))
59 {
60 ++epoch;
61
62 }
63 }
64
65
67 }
68
69 // Current merge key to stamp onto actions created this frame.
70 // 0 means "not mergeable".
71 auto current_merge_key() const -> uint64_t
72 {
73 return epoch;
74 }
75 };
76
77 static merge_session session;
78}
79
81{
82 auto& ev = ctx.get_cached<events>();
83
84 ev.on_play_before_begin.connect(sentinel_, 1000, this, &editing_manager::on_play_before_begin);
85 ev.on_play_after_end.connect(sentinel_, -1000, this, &editing_manager::on_play_after_end);
86 ev.on_frame_update.connect(sentinel_, 1000, this, &editing_manager::on_frame_update);
87 ev.on_script_recompile.connect(sentinel_, 1000, this, &editing_manager::on_script_recompile);
88
89 return true;
90}
91
93{
94 unselect();
95 unfocus();
96 return true;
97}
98
100{
101 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
102
103 waiting_for_compilation_before_play_ = true;
104
105
107
108 save_selection(ctx);
109
110 const auto& scenes = scene::get_all_scenes();
111 {
112 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "save_checkpoints");
113 caches_.clear();
114 for(auto scn : scenes)
115 {
116 auto& cache = caches_[scn->tag];
117 cache.scn = scn;
118 save_checkpoint(ctx, cache);
119 }
120 }
121
122
123 auto& scripting = ctx.get_cached<script_system>();
124 {
125 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "unload_app_domain");
126 scripting.unload_app_domain();
127 }
128 {
129 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "wait_for_jobs_to_finish");
130 scripting.wait_for_jobs_to_finish(ctx);
131 }
132 {
133 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "load_app_domain");
134 scripting.load_app_domain(ctx, true);
135 }
136
137 {
138 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "load_checkpoints");
139
140 for(auto scn : scenes)
141 {
142 auto& cache = caches_[scn->tag];
143 cache.scn = scn;
144 load_checkpoint(ctx, cache, true);
145 }
146 }
147
148 waiting_for_compilation_before_play_ = false;
149
150}
151
153{
154 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
155 unselect();
156
157 const auto& scenes = scene::get_all_scenes();
158 for(auto scn : scenes)
159 {
160 if(scn->tag == "game")
161 {
162 continue;
163 }
164
165 auto& cache = caches_[scn->tag];
166 cache.scn = scn;
167 save_checkpoint(ctx, cache);
168 }
169
170 auto& scripting = ctx.get_cached<script_system>();
171 scripting.unload_app_domain();
172 scripting.load_app_domain(ctx, false);
173
174 for(auto scn : scenes)
175 {
176 auto& cache = caches_[scn->tag];
177 cache.scn = scn;
178 load_checkpoint(ctx, cache, true);
179
180 sync_prefab_instances(ctx, scn);
181
182 }
183
184 caches_.clear();
185
187 pending_actions.clear();
188
189 ctx.get_cached<simulation>().set_time_scale(1.0f);
190}
191
192void editing_manager::on_script_recompile(rtti::context& ctx, const std::string& protocol, uint64_t version)
193{
194 if(waiting_for_compilation_before_play_)
195 {
196 return;
197 }
198
199 save_selection(ctx);
200
201 const auto& scenes = scene::get_all_scenes();
202 caches_.clear();
203 for(auto scn : scenes)
204 {
205 auto& cache = caches_[scn->tag];
206 cache.scn = scn;
207 save_checkpoint(ctx, cache);
208 }
209
210
211 auto& scripting = ctx.get_cached<script_system>();
212 scripting.unload_app_domain();
213 scripting.load_app_domain(ctx, false);
214
215 for(auto scn : scenes)
216 {
217 auto& cache = caches_[scn->tag];
218 cache.scn = scn;
219 load_checkpoint(ctx, cache, true);
220 }
221
222 caches_.clear();
223}
224
225void editing_manager::save_selection(rtti::context& ctx)
226{
227 selection_cache_ = {};
229 {
230 if(sel)
231 {
232 if(sel->valid())
233 {
234 auto& id_comp = sel->get_or_emplace<id_component>();
235 id_comp.generate_if_nil();
236 selection_cache_.uids.emplace_back(id_comp.id);
237 }
238 unselect(*sel);
239 }
240 }
241}
242
243void editing_manager::save_checkpoint(rtti::context& ctx, scene_cache& cache)
244{
245 if(!cache.scn)
246 {
247 return;
248 }
249 // APPLOG_TRACE("save_checkpoint {}", cache.scn->tag);
250
251 cache.cache = {};
252 cache.cache_source = cache.scn->source;
253 // first save scene
254 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "save_to_stream");
255
256 save_to_stream(cache.cache, *cache.scn);
257}
258
259void editing_manager::load_checkpoint(rtti::context& ctx, scene_cache& cache, bool recover_selection)
260{
261 if(!cache.scn)
262 {
263 return;
264 }
265
266 // APPLOG_TRACE("load_checkpoint {}", cache.scn->tag);
267 // clear scene
268 cache.scn->unload();
269
270 {
271 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "load_from_stream");
272 load_from_stream(cache.cache, *cache.scn);
273 }
274
275 cache.scn->source = cache.cache_source;
276
277 std::vector<entt::handle> entities;
278
279 {
280 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "load_checkpoint_selection");
281
282 cache.scn->registry->view<id_component>().each(
283 [&](auto e, auto&& comp)
284 {
285 auto uid = comp.id;
286 if(std::find(selection_cache_.uids.begin(), selection_cache_.uids.end(), uid) != selection_cache_.uids.end())
287 {
288 entities.emplace_back(cache.scn->create_handle(e));
289 }
290 });
291
292 for(auto entity : entities)
293 {
294 if(recover_selection)
295 {
296 entity.remove<id_component>();
298 }
299 }
300 }
301
302
303 {
304 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "load_checkpoint_update");
305 delta_t dt(0.016667f);
306
307 auto& rpath = ctx.get_cached<rendering_system>();
308 rpath.on_frame_update(*cache.scn, dt);
309 rpath.on_frame_before_render(*cache.scn, dt);
310 }
311
312
313 cache.scn = nullptr;
314}
315
317{
318 auto& ctx = engine::context();
319 auto& ec = ctx.get_cached<ecs>();
320 auto& ev = ctx.get_cached<events>();
321
322 if(ev.is_playing)
323 {
324 return;
325 }
326
327 auto& scn = ec.get_scene();
328
329 std::vector<entt::handle> affected_entities;
330 scn.registry->view<prefab_component>().each(
331 [&](auto e, auto&& prefab_comp)
332 {
333 auto entity = scn.create_handle(e);
334 if(prefab_comp.source == pfb)
335 {
336 affected_entities.emplace_back(entity);
337 }
338 });
339
340 for(auto& entity : affected_entities)
341 {
342 sync_prefab_entity(ctx, entity, pfb);
343 }
344}
345
347{
348 queue_action("Sync Prefab Entity",
349 [&ctx, entity, pfb]() mutable
350 {
351 auto& ec = ctx.get_cached<ecs>();
352 auto& ev = ctx.get_cached<events>();
353
354 if(ev.is_playing)
355 {
356 return;
357 }
358
359 if(!entity.valid())
360 {
361 return;
362 }
363
364 if(!pfb.is_valid())
365 {
366 return;
367 }
368
369 auto& scn = ec.get_scene();
370 if(auto trans_comp = entity.template try_get<transform_component>())
371 {
372 auto parent = trans_comp->get_parent();
373 auto pos = trans_comp->get_position_local();
374 auto rot = trans_comp->get_rotation_local();
375
376 auto& prefab_comp = entity.get<prefab_component>();
377 // Enable path recording for prefab loading
379 path_ctx.should_serialize_property_callback = [&](const std::string& property_path) -> bool
380 {
381 return !prefab_comp.has_serialization_override(property_path);
382 };
383 path_ctx.enable_recording();
386
387
388 if(scn.instantiate_out(pfb, entity))
389 {
390 auto& new_trans = entity.get<transform_component>();
391 new_trans.set_position_local(pos);
392 new_trans.set_rotation_local(rot);
393
394 new_trans.set_parent(parent, false);
395 }
396
397
398 // Restore previous path context
400 }
401
402 });
403}
404
406{
407 scn->registry->view<prefab_component>().each(
408 [&](auto e, auto&& comp)
409 {
410 sync_prefab_entity(ctx, comp.get_owner(), comp.source);
411 });
412
413}
414
416{
418
419 if(ImGui::IsKeyDown(ImGuiKey_LeftShift))
420 {
422 }
423 if(ImGui::IsKeyDown(ImGuiKey_LeftCtrl))
424 {
426 }
427
428 return mode;
429}
430
432{
433 session.tick();
434
436
437 if(focused_data.frames > 0)
438 {
440 }
441
442 if(focused_data.frames == 0)
443 {
444 unfocus();
445 }
446}
447void editing_manager::focus(entt::meta_any object)
448{
450 focused_data.frames = 20;
451}
452
453void editing_manager::focus_path(const fs::path& object)
454{
456}
457
458void editing_manager::unselect(bool clear_selection_tools)
459{
460 selection_data = {};
461
462 if(clear_selection_tools)
463 {
464 ImGuizmo::Enable(false);
465 ImGuizmo::Enable(true);
466 }
467}
468
470{
471 focused_data = {};
472}
473
475{
476 auto& ev = ctx.get_cached<events>();
477
478 if(ev.is_playing)
479 {
480 return;
481 }
482
483
484 auto on_continue = [this,&ctx, prefab]()
485 {
486 // Store the prefab we're editing
489
490 // Clear selection
491 unselect();
492
493 // Create a new scene for prefab editing if it doesn't exist
495
496 // Set up a default 3D scene with lighting
498
499 // Instantiate the prefab in our editing scene
501
502 // Select the prefab entity
503 if (prefab_entity)
504 {
506 }
507
508 APPLOG_INFO("Entered prefab editing mode for: {}", prefab.id());
509 };
510
511 if (is_prefab_mode())
512 {
513 // Already in prefab mode, check if we need to save changes
514 if (edited_prefab != prefab)
515 {
516 auto on_save = [this,&ctx]()
517 {
519 };
520
521 if(auto_save)
522 {
523 on_save();
524 }
525 else
526 {
527 prompt_save_changes(ctx, on_save, on_continue);
528 return;
529 }
530 }
531 else
532 {
534
535 // Already editing this prefab, nothing to do
536 return;
537 }
538 }
539
540 on_continue();
541
542
543}
544
545auto editing_manager::prompt_save_changes(rtti::context& ctx, const std::function<void()>& on_save, const std::function<void()>& on_continue) -> bool
546{
547 ImBox::ShowSaveConfirmation("Save prefab?",
548 "Do you want to save the changes you made?",
549 [&ctx, on_save, on_continue](ImBox::ModalResult result)
550 {
551 if(result == ImBox::ModalResult::Save)
552 {
553 on_save();
554 }
555
556 if(result != ImBox::ModalResult::Cancel)
557 {
558 on_continue();
559 }
560 });
561
562 return true;
563}
564
566{
567 if (!is_prefab_mode())
568 {
569 return;
570 }
571
572 auto on_save = [this,&ctx]()
573 {
575 };
576
577 auto on_continue = [this, &ctx]()
578 {
579 // Reset state
581 edited_prefab = {};
582 prefab_entity = {};
584
585 // Clear selection
586 unselect();
587
588 APPLOG_INFO("Exited prefab editing mode");
589 };
590
591 switch (save_changes)
592 {
593 case save_option::yes:
594 on_save();
595 on_continue();
596 break;
597
598 case save_option::no:
599 on_continue();
600 break;
601
603 prompt_save_changes(ctx, on_save, on_continue);
604 break;
605 }
606
607 // if (should_save)
608 // {
609 // save_prefab_changes(ctx);
610 // }
611
612 // // Reset state
613 // current_mode = editing_mode::scene;
614 // edited_prefab = {};
615 // prefab_entity = {};
616 // prefab_scene.unload();
617
618 // // Clear selection
619 // unselect();
620
621 // APPLOG_INFO("Exited prefab editing mode");
622}
623
625{
627 {
628 return;
629 }
630
631 // Make sure the entity is valid
632 if (!prefab_entity.valid())
633 {
634 APPLOG_ERROR("Failed to save prefab: Invalid entity");
635 ImGui::PushNotification(ImGuiToast(ImGuiToastType_Error, 1000,"Failed to save prefab."));
636
637 return;
638 }
639
640 auto prefab_path = fs::resolve_protocol(edited_prefab.id());
642
643 APPLOG_INFO("Saved changes to prefab: {}", edited_prefab.id());
645
646}
647
648
650{
651 if (is_prefab_mode())
652 {
653 return &prefab_scene;
654 }
655
656 auto& ec = ctx.get_cached<ecs>();
657 return &ec.get_scene();
658
659}
660
662{
664 unselect();
665 unfocus();
666
667 // Clear pending actions and undo/redo stack
668 pending_actions.clear();
670
671 // If in prefab mode, exit it
672 if (is_prefab_mode())
673 {
674 auto& ctx = engine::context();
676 }
677
678 // Reset prefab editing mode and clean up all references
680 edited_prefab = {};
681 prefab_entity = {};
682
683}
684
685
686void editing_manager::do_action(const std::string& name, const std::function<void()>& action)
687{
689}
690
691void editing_manager::do_action(const std::string& name, const std::function<void()>& do_action, const std::function<void()>& undo_action)
692{
694}
695
696void editing_manager::do_action(const std::string& name, std::shared_ptr<editing_action_t> action)
697{
698 return add_action(name, action, true);
699}
700
701void editing_manager::queue_action(const std::string& name, const std::function<void()>& action)
702{
704}
705
706void editing_manager::queue_action(const std::string& name, const std::function<void()>& do_action, const std::function<void()>& undo_action)
707{
709}
710
711void editing_manager::queue_action(const std::string& name, std::shared_ptr<editing_action_t> action)
712{
713 return add_action(name, action, false);
714}
715
716
717void editing_manager::add_action(const std::string& name, std::shared_ptr<editing_action_t> action, bool immediate)
718{
719 if (!action)
720 {
721 return;
722 }
723
724 has_unsaved_changes_ = true;
725
726
727 action->merge_key = session.current_merge_key();
728
729 if(!name.empty())
730 {
731 action->name = name;
732 }
733
734 if(undo_stack_enabled.empty())
735 {
736 action->undoable = false;
737 }
738 else
739 {
740 action->undoable = undo_stack_enabled.top();
741 }
742
743 // Queue the action for execution (don't execute immediately)
744 pending_actions.push_back(std::move(action));
745
746 if(immediate)
747 {
749 }
750}
751
752
754{
755 undo_stack_enabled.push(enabled);
756}
761
763{
764 // Process all pending actions
765 for (auto& action : pending_actions)
766 {
767 if (action)
768 {
769 // Execute the action
770 action->execution_count++;
771 action->do_action();
772 // Add to undo stack if the action is undoable
773 // Note: We need to handle merging here since the action is now executed
774 if (action->is_undoable())
775 {
776 // Move the action to the undo stack
777 undo_stack.push_if_undoable(std::move(action));
778 }
779 }
780 }
781
782 // Clear the pending actions queue
783 pending_actions.clear();
784}
785
787{
788 if (undo_stack.can_undo())
789 {
790 has_unsaved_changes_ = true;
792 }
793}
794
796{
797 if (undo_stack.can_redo())
798 {
799 has_unsaved_changes_ = true;
801 }
802}
803
804} // namespace unravel
const btCollisionObject * object
Component that handles transformations (position, rotation, scale, etc.) in the ACE framework.
void set_position_local(const math::vec3 &position) noexcept
Sets the local position.
std::chrono::duration< float > delta_t
uint64_t epoch
bool down_prev
std::string name
Definition hub.cpp:27
@ ImGuiToastType_Error
@ ImGuiToastType_Success
#define APPLOG_ERROR(...)
Definition logging.h:20
#define APPLOG_INFO(...)
Definition logging.h:18
#define APPLOG_TRACE(...)
Definition logging.h:17
ModalResult
Modal result flags for message box buttons.
auto ShowSaveConfirmation(const std::string &title, const std::string &message, std::function< void(ModalResult)> callback) -> std::shared_ptr< MsgBox >
Show a save confirmation dialog with Save/Don't Save/Cancel buttons.
NOTIFY_INLINE void PushNotification(const ImGuiToast &toast)
Insert a new toast in the list.
path resolve_protocol(const path &_path)
Given the specified path/filename, resolve the final full filename. This will be based on either the ...
void set_path_context(path_context *ctx)
auto get_path_context() -> path_context *
auto atomic_save_to_file(const fs::path &key, const asset_handle< T > &obj) -> bool
auto load_from_stream(std::istream &stream, entt::handle e, script_component::script_object &obj) -> bool
auto save_to_stream(std::ostream &stream, entt::const_handle e, const script_component::script_object &obj) -> bool
entt::entity entity
Represents a handle to an asset, providing access and management functions.
auto is_valid() const -> bool
Checks if the handle is valid.
auto get_cached() -> T &
Definition context.hpp:49
std::function< bool(const std::string &)> should_serialize_property_callback
static void create_default_3d_scene_for_editing(rtti::context &ctx, scene &scn)
Creates a default 3D scene for editing.
Definition defaults.cpp:659
Manages the entity-component-system (ECS) operations for the ACE framework.
Definition ecs.h:12
auto get_scene() -> scene &
Gets the current scene.
Definition ecs.cpp:30
auto is_prefab_mode() const -> bool
auto get_active_scene(rtti::context &ctx) -> scene *
void on_play_after_end(rtti::context &ctx)
auto try_get_selections_as() const -> std::vector< const T * >
auto get_select_mode() const -> select_mode
void push_undo_stack_enabled(bool enabled)
ImGuizmo::MODE mode
current manipulation gizmo space.
void focus_path(const fs::path &object)
auto init(rtti::context &ctx) -> bool
void on_play_before_begin(rtti::context &ctx)
void on_frame_update(rtti::context &ctx, delta_t)
void on_script_recompile(rtti::context &ctx, const std::string &protocol, uint64_t version)
std::vector< std::shared_ptr< editing_action_t > > pending_actions
void sync_prefab_entity(rtti::context &ctx, entt::handle entity, const asset_handle< prefab > &pfb)
selection selection_data
selection data containing selected object
void queue_action(const std::string &name, const std::function< void()> &action)
void do_action(const std::string &name, const std::function< void()> &action)
void sync_prefab_instances(rtti::context &ctx, scene *scn)
void on_prefab_updated(const asset_handle< prefab > &pfb)
void enter_prefab_mode(rtti::context &ctx, const asset_handle< prefab > &prefab, bool auto_save=false)
std::stack< bool > undo_stack_enabled
void select(const T &entry, select_mode mode=select_mode::normal)
auto deinit(rtti::context &ctx) -> bool
void add_action(const std::string &name, std::shared_ptr< editing_action_t > action, bool immediate=true)
void focus(entt::meta_any object)
Selects an object. Can be anything.
void exit_prefab_mode(rtti::context &ctx, save_option save_changes=save_option::prompt)
void save_prefab_changes(rtti::context &ctx)
asset_handle< prefab > edited_prefab
void unselect(bool clear_selection_tools=true)
Clears the selection data.
static auto context() -> rtti::context &
Definition engine.cpp:116
hpp::event< void(rtti::context &)> on_play_before_begin
engine play events
Definition events.h:23
Component that holds a reference to a prefab asset and tracks property overrides.
Represents a generic prefab with a buffer for serialized data.
Definition prefab.h:18
Represents a scene in the ACE framework, managing entities and their relationships.
Definition scene.h:21
auto instantiate(const asset_handle< prefab > &pfb) -> entt::handle
Definition scene.cpp:195
static auto get_all_scenes() -> const std::vector< scene * > &
Definition scene.cpp:119
void unload()
Unloads the scene, removing all entities.
Definition scene.cpp:172
std::unique_ptr< entt::registry > registry
The registry that manages all entities in the scene.
Definition scene.h:117
Class responsible for timers.
Definition simulation.h:20
void push_if_undoable(std::shared_ptr< editing_action_t > action)
auto can_undo() const -> bool
auto can_redo() const -> bool
cache_t cache
Definition uniform.cpp:15