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"
5#include <chrono>
10
11#include <engine/ecs/ecs.h>
13#include <engine/engine.h>
14#include <engine/events.h>
17
19#include <editor/events.h>
22#include <imgui_widgets/gizmo.h>
25
26#include <filedialog/filedialog.h>
27
28namespace unravel
29{
30
31namespace
32{
33 struct merge_session
34 {
35 uint64_t epoch = 1; // increments on boundaries (press/release/focus loss)
36 bool down_prev = false;
37
38 auto is_active() const -> bool
39 {
40 return ImGui::IsMouseDown(ImGuiMouseButton_Left) || ImGui::IsAnyItemActive();
41 }
42
43 void tick()
44 {
45 const bool down = is_active();
46
47
48 if(!ImGui::IsAnyItemActive())
49 {
50 // Bump the epoch on any boundary so new actions won't merge with the previous batch.
51 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
52 ImGui::GetIO().AppFocusLost)
53 {
54 ++epoch;
55 }
56
57 if(ImGui::IsMouseReleased(ImGuiMouseButton_Left))
58 {
59 ++epoch;
60
61 }
62 }
63
64
66 }
67
68 // Current merge key to stamp onto actions created this frame.
69 // 0 means "not mergeable".
70 auto current_merge_key() const -> uint64_t
71 {
72 return epoch;
73 }
74 };
75
76 static merge_session session;
77}
78
80{
81 auto& ev = ctx.get_cached<events>();
82
83 ev.on_play_before_begin.connect(sentinel_, 1000, this, &editing_manager::on_play_before_begin);
84 ev.on_play_after_end.connect(sentinel_, -1000, this, &editing_manager::on_play_after_end);
85 ev.on_frame_update.connect(sentinel_, 1000, this, &editing_manager::on_frame_update);
86 ev.on_script_recompile.connect(sentinel_, 1000, this, &editing_manager::on_script_recompile);
87
88 return true;
89}
90
92{
93 unselect();
94 unfocus();
95 return true;
96}
97
99{
100 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
101
102 waiting_for_compilation_before_play_ = true;
103
104
106
107 save_selection(ctx);
108
109 const auto& scenes = scene::get_all_scenes();
110 {
111 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "save_checkpoints");
112 caches_.clear();
113 for(auto scn : scenes)
114 {
115 auto& cache = caches_[scn->tag];
116 cache.scn = scn;
117 save_checkpoint(ctx, cache);
118 }
119 }
120
121
122 auto& scripting = ctx.get_cached<script_system>();
123 {
124 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "unload_app_domain");
125 scripting.unload_app_domain();
126 }
127 {
128 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "wait_for_jobs_to_finish");
129 scripting.wait_for_jobs_to_finish(ctx);
130 }
131 {
132 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "load_app_domain");
133 scripting.load_app_domain(ctx, true);
134 }
135
136 {
137 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "load_checkpoints");
138
139 for(auto scn : scenes)
140 {
141 auto& cache = caches_[scn->tag];
142 cache.scn = scn;
143 load_checkpoint(ctx, cache, true);
144 }
145 }
146
147 waiting_for_compilation_before_play_ = false;
148
149}
150
152{
153 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
154 unselect();
155
156 const auto& scenes = scene::get_all_scenes();
157 for(auto scn : scenes)
158 {
159 if(scn->tag == "game")
160 {
161 continue;
162 }
163
164 auto& cache = caches_[scn->tag];
165 cache.scn = scn;
166 save_checkpoint(ctx, cache);
167 }
168
169 auto& scripting = ctx.get_cached<script_system>();
170 scripting.unload_app_domain();
171 scripting.load_app_domain(ctx, false);
172
173 for(auto scn : scenes)
174 {
175 auto& cache = caches_[scn->tag];
176 cache.scn = scn;
177 load_checkpoint(ctx, cache, true);
178
179 sync_prefab_instances(ctx, scn);
180
181 }
182
183 caches_.clear();
184
186 pending_actions.clear();
187}
188
189void editing_manager::on_script_recompile(rtti::context& ctx, const std::string& protocol, uint64_t version)
190{
191 if(waiting_for_compilation_before_play_)
192 {
193 return;
194 }
195
196 save_selection(ctx);
197
198 const auto& scenes = scene::get_all_scenes();
199 caches_.clear();
200 for(auto scn : scenes)
201 {
202 auto& cache = caches_[scn->tag];
203 cache.scn = scn;
204 save_checkpoint(ctx, cache);
205 }
206
207
208 auto& scripting = ctx.get_cached<script_system>();
209 scripting.unload_app_domain();
210 scripting.load_app_domain(ctx, false);
211
212 for(auto scn : scenes)
213 {
214 auto& cache = caches_[scn->tag];
215 cache.scn = scn;
216 load_checkpoint(ctx, cache, true);
217 }
218
219 caches_.clear();
220}
221
222void editing_manager::save_selection(rtti::context& ctx)
223{
224 selection_cache_ = {};
226 {
227 if(sel)
228 {
229 if(sel->valid())
230 {
231 auto& id_comp = sel->get_or_emplace<id_component>();
232 id_comp.generate_if_nil();
233 selection_cache_.uids.emplace_back(id_comp.id);
234 }
235 unselect(*sel);
236 }
237 }
238}
239
240void editing_manager::save_checkpoint(rtti::context& ctx, scene_cache& cache)
241{
242 if(!cache.scn)
243 {
244 return;
245 }
246 // APPLOG_TRACE("save_checkpoint {}", cache.scn->tag);
247
248 cache.cache = {};
249 cache.cache_source = cache.scn->source;
250 // first save scene
251 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "save_to_stream");
252
253 save_to_stream(cache.cache, *cache.scn);
254}
255
256void editing_manager::load_checkpoint(rtti::context& ctx, scene_cache& cache, bool recover_selection)
257{
258 if(!cache.scn)
259 {
260 return;
261 }
262
263 // APPLOG_TRACE("load_checkpoint {}", cache.scn->tag);
264 // clear scene
265 cache.scn->unload();
266
267 {
268 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "load_from_stream");
269 load_from_stream(cache.cache, *cache.scn);
270 }
271
272 cache.scn->source = cache.cache_source;
273
274 std::vector<entt::handle> entities;
275
276 {
277 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "load_checkpoint_selection");
278
279 cache.scn->registry->view<id_component>().each(
280 [&](auto e, auto&& comp)
281 {
282 auto uid = comp.id;
283 if(std::find(selection_cache_.uids.begin(), selection_cache_.uids.end(), uid) != selection_cache_.uids.end())
284 {
285 entities.emplace_back(cache.scn->create_handle(e));
286 }
287 });
288
289 for(auto entity : entities)
290 {
291 if(recover_selection)
292 {
293 entity.remove<id_component>();
295 }
296 }
297 }
298
299
300 {
301 // APPLOG_TRACE_PERF_NAMED(std::chrono::milliseconds, "load_checkpoint_update");
302 delta_t dt(0.016667f);
303
304 auto& rpath = ctx.get_cached<rendering_system>();
305 rpath.on_frame_update(*cache.scn, dt);
306 rpath.on_frame_before_render(*cache.scn, dt);
307 }
308
309
310 cache.scn = nullptr;
311}
312
314{
315 auto& ctx = engine::context();
316 auto& ec = ctx.get_cached<ecs>();
317 auto& ev = ctx.get_cached<events>();
318
319 if(ev.is_playing)
320 {
321 return;
322 }
323
324 auto& scn = ec.get_scene();
325
326 std::vector<entt::handle> affected_entities;
327 scn.registry->view<prefab_component>().each(
328 [&](auto e, auto&& prefab_comp)
329 {
330 auto entity = scn.create_handle(e);
331 if(prefab_comp.source == pfb)
332 {
333 affected_entities.emplace_back(entity);
334 }
335 });
336
337 for(auto& entity : affected_entities)
338 {
339 sync_prefab_entity(ctx, entity, pfb);
340 }
341}
342
344{
345 queue_action("Sync Prefab Entity",
346 [&ctx, entity, pfb]() mutable
347 {
348 auto& ec = ctx.get_cached<ecs>();
349 auto& ev = ctx.get_cached<events>();
350
351 if(ev.is_playing)
352 {
353 return;
354 }
355
356 if(!entity.valid())
357 {
358 return;
359 }
360
361 if(!pfb.is_valid())
362 {
363 return;
364 }
365
366 auto& scn = ec.get_scene();
367 if(auto trans_comp = entity.template try_get<transform_component>())
368 {
369 auto parent = trans_comp->get_parent();
370 auto pos = trans_comp->get_position_local();
371 auto rot = trans_comp->get_rotation_local();
372
373 auto& prefab_comp = entity.get<prefab_component>();
374 // Enable path recording for prefab loading
376 path_ctx.should_serialize_property_callback = [&](const std::string& property_path) -> bool
377 {
378 return !prefab_comp.has_serialization_override(property_path);
379 };
380 path_ctx.enable_recording();
383
384
385 if(scn.instantiate_out(pfb, entity))
386 {
387 auto& new_trans = entity.get<transform_component>();
388 new_trans.set_position_local(pos);
389 new_trans.set_rotation_local(rot);
390
391 new_trans.set_parent(parent, false);
392 }
393
394
395 // Restore previous path context
397 }
398
399 });
400}
401
403{
404 scn->registry->view<prefab_component>().each(
405 [&](auto e, auto&& comp)
406 {
407 sync_prefab_entity(ctx, comp.get_owner(), comp.source);
408 });
409
410}
411
413{
415
416 if(ImGui::IsKeyDown(ImGuiKey_LeftShift))
417 {
419 }
420 if(ImGui::IsKeyDown(ImGuiKey_LeftCtrl))
421 {
423 }
424
425 return mode;
426}
427
429{
430 session.tick();
431
433
434 if(focused_data.frames > 0)
435 {
437 }
438
439 if(focused_data.frames == 0)
440 {
441 unfocus();
442 }
443}
444void editing_manager::focus(entt::meta_any object)
445{
447 focused_data.frames = 20;
448}
449
450void editing_manager::focus_path(const fs::path& object)
451{
453}
454
455void editing_manager::unselect(bool clear_selection_tools)
456{
457 selection_data = {};
458
459 if(clear_selection_tools)
460 {
461 ImGuizmo::Enable(false);
462 ImGuizmo::Enable(true);
463 }
464}
465
467{
468 focused_data = {};
469}
470
472{
473 auto& ev = ctx.get_cached<events>();
474
475 if(ev.is_playing)
476 {
477 return;
478 }
479
480
481 auto on_continue = [this,&ctx, prefab]()
482 {
483 // Store the prefab we're editing
486
487 // Clear selection
488 unselect();
489
490 // Create a new scene for prefab editing if it doesn't exist
492
493 // Set up a default 3D scene with lighting
495
496 // Instantiate the prefab in our editing scene
498
499 // Select the prefab entity
500 if (prefab_entity)
501 {
503 }
504
505 APPLOG_INFO("Entered prefab editing mode for: {}", prefab.id());
506 };
507
508 if (is_prefab_mode())
509 {
510 // Already in prefab mode, check if we need to save changes
511 if (edited_prefab != prefab)
512 {
513 auto on_save = [this,&ctx]()
514 {
516 };
517
518 if(auto_save)
519 {
520 on_save();
521 }
522 else
523 {
524 prompt_save_changes(ctx, on_save, on_continue);
525 return;
526 }
527 }
528 else
529 {
531
532 // Already editing this prefab, nothing to do
533 return;
534 }
535 }
536
537 on_continue();
538
539
540}
541
542auto editing_manager::prompt_save_changes(rtti::context& ctx, const std::function<void()>& on_save, const std::function<void()>& on_continue) -> bool
543{
544 ImBox::ShowSaveConfirmation("Save prefab?",
545 "Do you want to save the changes you made?",
546 [&ctx, on_save, on_continue](ImBox::ModalResult result)
547 {
548 if(result == ImBox::ModalResult::Save)
549 {
550 on_save();
551 }
552
553 if(result != ImBox::ModalResult::Cancel)
554 {
555 on_continue();
556 }
557 });
558
559 return true;
560}
561
563{
564 if (!is_prefab_mode())
565 {
566 return;
567 }
568
569 auto on_save = [this,&ctx]()
570 {
572 };
573
574 auto on_continue = [this, &ctx]()
575 {
576 // Reset state
578 edited_prefab = {};
579 prefab_entity = {};
581
582 // Clear selection
583 unselect();
584
585 APPLOG_INFO("Exited prefab editing mode");
586 };
587
588 switch (save_changes)
589 {
590 case save_option::yes:
591 on_save();
592 on_continue();
593 break;
594
595 case save_option::no:
596 on_continue();
597 break;
598
600 prompt_save_changes(ctx, on_save, on_continue);
601 break;
602 }
603
604 // if (should_save)
605 // {
606 // save_prefab_changes(ctx);
607 // }
608
609 // // Reset state
610 // current_mode = editing_mode::scene;
611 // edited_prefab = {};
612 // prefab_entity = {};
613 // prefab_scene.unload();
614
615 // // Clear selection
616 // unselect();
617
618 // APPLOG_INFO("Exited prefab editing mode");
619}
620
622{
624 {
625 return;
626 }
627
628 // Make sure the entity is valid
629 if (!prefab_entity.valid())
630 {
631 APPLOG_ERROR("Failed to save prefab: Invalid entity");
632 ImGui::PushNotification(ImGuiToast(ImGuiToastType_Error, 1000,"Failed to save prefab."));
633
634 return;
635 }
636
637 auto prefab_path = fs::resolve_protocol(edited_prefab.id());
639
640 APPLOG_INFO("Saved changes to prefab: {}", edited_prefab.id());
642
643}
644
645
647{
648 if (is_prefab_mode())
649 {
650 return &prefab_scene;
651 }
652
653 auto& ec = ctx.get_cached<ecs>();
654 return &ec.get_scene();
655
656}
657
659{
661 unselect();
662 unfocus();
663
664 // Clear pending actions and undo/redo stack
665 pending_actions.clear();
667
668 // If in prefab mode, exit it
669 if (is_prefab_mode())
670 {
671 auto& ctx = engine::context();
673 }
674
675 // Reset prefab editing mode and clean up all references
677 edited_prefab = {};
678 prefab_entity = {};
679
680}
681
682
683void editing_manager::do_action(const std::string& name, const std::function<void()>& action)
684{
686}
687
688void editing_manager::do_action(const std::string& name, const std::function<void()>& do_action, const std::function<void()>& undo_action)
689{
691}
692
693void editing_manager::do_action(const std::string& name, std::shared_ptr<editing_action_t> action)
694{
695 return add_action(name, action, true);
696}
697
698void editing_manager::queue_action(const std::string& name, const std::function<void()>& action)
699{
701}
702
703void editing_manager::queue_action(const std::string& name, const std::function<void()>& do_action, const std::function<void()>& undo_action)
704{
706}
707
708void editing_manager::queue_action(const std::string& name, std::shared_ptr<editing_action_t> action)
709{
710 return add_action(name, action, false);
711}
712
713
714void editing_manager::add_action(const std::string& name, std::shared_ptr<editing_action_t> action, bool immediate)
715{
716 if (!action)
717 {
718 return;
719 }
720
721 has_unsaved_changes_ = true;
722
723
724 action->merge_key = session.current_merge_key();
725
726 if(!name.empty())
727 {
728 action->name = name;
729 }
730
731 if(undo_stack_enabled.empty())
732 {
733 action->undoable = false;
734 }
735 else
736 {
737 action->undoable = undo_stack_enabled.top();
738 }
739
740 // Queue the action for execution (don't execute immediately)
741 pending_actions.push_back(std::move(action));
742
743 if(immediate)
744 {
746 }
747}
748
749
751{
752 undo_stack_enabled.push(enabled);
753}
758
760{
761 // Process all pending actions
762 for (auto& action : pending_actions)
763 {
764 if (action)
765 {
766 // Execute the action
767 action->execution_count++;
768 action->do_action();
769 // Add to undo stack if the action is undoable
770 // Note: We need to handle merging here since the action is now executed
771 if (action->is_undoable())
772 {
773 // Move the action to the undo stack
774 undo_stack.push_if_undoable(std::move(action));
775 }
776 }
777 }
778
779 // Clear the pending actions queue
780 pending_actions.clear();
781}
782
784{
785 if (undo_stack.can_undo())
786 {
787 has_unsaved_changes_ = true;
789 }
790}
791
793{
794 if (undo_stack.can_redo())
795 {
796 has_unsaved_changes_ = true;
798 }
799}
800
801} // 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:635
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:115
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:192
static auto get_all_scenes() -> const std::vector< scene * > &
Definition scene.cpp:119
void unload()
Unloads the scene, removing all entities.
Definition scene.cpp:169
std::unique_ptr< entt::registry > registry
The registry that manages all entities in the scene.
Definition scene.h:117
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