-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathSceneManager.cpp
More file actions
2040 lines (1801 loc) · 81.5 KB
/
Copy pathSceneManager.cpp
File metadata and controls
2040 lines (1801 loc) · 81.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "SceneManager.h"
#include "RenderScene.h"
#include "Scene.h"
#include "MeshRenderer.h"
#include "FoliageComponent.h"
#include "Material.h"
#include "Object.h"
#include "Transform.h" // 레인 2: Entity::GetComponent<Transform>() 직접 참조
#include "BoneComponent.h" // E7-b: 뼈 구파일 승격(Entity::AddComponent<BoneComponent>())
// 프리팹 재연결은 이 헤더로 한다. PrefabEditor는 저작 도구라 Editor 소유이고
// (E3-4에서 EngineEntry로 옮겼다) Core는 Editor를 물지 않는다 — Prefab.cpp도
// 같은 이유로 이 헤더를 쓴다(SceneGraphRedesignPlan P2).
//
// 여기 있던 "PrefabEditor.h는 DYNAMICCPP_EXPORTS로 가드돼 있어 못 쓴다"는 설명은
// 두 번 낡아 있었다: C++ 핫리로드가 은퇴해 그 매크로를 정의하는 곳이 하나도 없어
// 가드가 무력했고(솔루션에 Dynamic_CPP 프로젝트 자체가 없다), 이제는 층이 갈렸다.
#include "PrefabUtility.h"
#include "DataSystem.h"
#include "ComponentFactory.h"
#include "AuthoringDocumentAccess.h"
#include "AuthoringParsedDocument.h"
#include "EntityAuthoringRead.h" // D3-a-2: 저작 읽기 어댑터
#include "RegisterReflectManual.h" // CT4: 명시 메타 이전 타입의 등록 (def 스캔 밖)
#include "Profiler.h"
#include "SerializationProfiler.h" // D0: 직렬화 기준선 계측
#include "InputActionManager.h"
#include "TagManager.h"
#include "ReflectionRegister.h"
#include <algorithm>
#include <stdexcept>
#include <unordered_map>
#include <unordered_set>
#include "Component.h"
#include "TimeSystem.h"
#include "GpuDiagnostics.h"
#include "ScriptComponent.h"
#include "ClrHost.h"
// 예전에는 여기에 SuspendSceneScripts가 있었다. 재생 시작이 에디터 씬을 복제해
// PlayScene을 만들던 시절, 원본과 사본의 스크립트 인스턴스가 둘 다 틱을 받아
// 로직이 두 벌 도는 것을 막는 봉합이었다. 씬을 복제하지 않게 되면서 두 벌이
// 생길 여지 자체가 사라져 걷어냈다.
namespace
{
// Scene 공개/리플렉션 API는 Entity 기준 이름(m_Entities)으로 저장한다.
// 다만 기존 .creator 자산은 m_SceneObjects 키를 갖고 있으므로 읽을 때만
// 구 키를 별칭으로 허용한다. 새 저장 결과는 항상 m_Entities 하나로 수렴한다.
Authoring::ReadNode SerializedEntities(const Authoring::ReadNode& sceneNode)
{
if (!sceneNode) return {};
if (Authoring::ReadNode entities = sceneNode["m_Entities"])
return entities;
return sceneNode["m_SceneObjects"];
}
Authoring::ParsedDocument ParseSceneDocument(const std::string& path)
{
const file::path sourcePath(path);
file::path readPath = sourcePath;
FileGuid assetGuid{};
if (DataSystems)
{
assetGuid = DataSystems->GetFileGuid(sourcePath);
if (assetGuid != FileGuid{})
{
readPath = DataSystems->ResolveCatalogAssetPath(assetGuid);
if (readPath.empty())
{
throw std::runtime_error(
"씬 cooked runtime artifact 해석 실패: " + path);
}
}
else if (!PathFinder::IsAssetAuthoringEnabled())
{
throw std::runtime_error(
"Player 씬 source identity 해석 실패: " + path);
}
}
std::string error;
Authoring::ParsedDocument document =
Authoring::ParsedDocument::ParseFile(readPath.string(), error);
if (!document)
{
throw std::runtime_error("씬 runtime 문서 파싱 실패: "
+ readPath.string()
+ " (" + error + ")");
}
if (assetGuid != FileGuid{})
{
const bool cooked = readPath.lexically_normal()
!= sourcePath.lexically_normal();
std::printf("[scene.document] source=%s guid=%s\n",
cooked ? "cooked" : "authoring", assetGuid.ToString().c_str());
}
return document;
}
// 프리팹 인스턴스 재연결 (SceneGraphRedesignPlan §4 트랙 P, P2).
//
// 반드시 RemapLoadBatchIndices 이후에 불러야 한다 — 그 전에는 obj->m_parentIndex가
// 아직 파일 인덱스 스킴이라(RemapLoadBatchIndices 선언부 주석 참고) 부모를 슬롯
// 인덱스로 찾을 수 없다. 옛 주석 처리 블록(P-a, DesirealizeGameObject 안에 있던
// `//PrefabUtilitys->LoadPrefabGuid(...)`)처럼 오브젝트를 하나씩 역직렬화하며
// 처리하지 않고, 로드 배치가 끝난 뒤 한 번에 훑는 이유이기도 하다.
//
// 인스턴스 "루트" 판정은 옛 parent==0 관습(Prefab::InstantiateRecursive가 최초
// 호출에만 넘기던 인자값에 우연히 기대던 것 — 인덱스 0이 "부모 없음"이 아니라
// 그 자체로 하나의 실제 슬롯이라는 사실과 부딪힌다)을 되살리지 않는다. 대신
// P1이 만든 명시 데이터로 한다: 부모가 없거나 부모의 m_prefabFileGuid가 나와
// 다르면 내가 그 프리팹 인스턴스의 루트다 — 같은 프리팹 안의 자식은 부모와
// guid가 같다(Prefab::InstantiateRecursive가 root/children 전원에 같은 guid를
// 매기는 것과 대칭). 인스턴스 루트를 나중에 다른 오브젝트 밑으로 재부모화해도
// 이 판정은 무너지지 않는다.
void ReconnectPrefabInstance(Scene* scene, Entity* obj)
{
if (!scene || !obj || obj->m_prefabFileGuid == nullFileGuid)
return;
Prefab* prefab = PrefabUtilitys->LoadPrefabGuid(obj->m_prefabFileGuid);
if (!prefab)
{
// 프리팹 파일이 삭제됐거나 GUID가 끊겼다 — 연결 없이 오브젝트는 그대로 살려 둔다.
Debug->LogWarning("프리팹 재연결 실패(파일을 찾을 수 없음): " + obj->GetHashedName().ToString());
return;
}
obj->m_prefab = prefab;
bool isInstanceRoot = true;
const Entity::Index parentIndex = obj->GetParentIndex();
if (parentIndex != Entity::INVALID_INDEX)
{
if (auto parentObj = scene->TryGetEntity(parentIndex))
{
isInstanceRoot = (parentObj->m_prefabFileGuid != obj->m_prefabFileGuid);
}
}
if (isInstanceRoot)
{
PrefabUtilitys->RegisterInstance(obj, prefab);
}
}
}
// 구파일 승격 공유 헬퍼(레인 2, SceneGraphRedesignPlan §5 예외 4).
//
// 레인 1이 Transform을 Component 파생으로 승격하면서 GameObject
// 스키마에서 m_transform 필드가 빠진다. 역직렬화기는 모르는 키를 조용히
// 무시하므로(ReflectionTypedYml.h ReadMember) 구파일(현재 저작 자산
// 218개 전부 — 씬 12·프리팹 206)의 m_transform 노드를 방치하면
// 위치·회전·크기가 에러 없이 사라진다. GameObject를 역직렬화하는 5개
// 호출부 중 실제로 파일에서 읽어 들이는 4곳(SceneManager.cpp 3곳·
// Prefab.cpp 1곳)이 이 함수를 부른다. Object.cpp의 Instantiate 클론
// 경로는 살아있는 오브젝트를 그 자리에서 Meta::Serialize로 재직렬화한
// 인메모리 노드를 읽으므로 이미 새 스키마다 — m_transform 키가 나올 수
// 없어 승격 대상이 아니다(Object.cpp 주석 참고).
//
// 헤더를 새로 두지 않고(배정 파일 밖 편집 금지 — SceneManager.h는
// 레인 2 배정 밖이다) 외부 링키지 자유 함수로 노출한다. Prefab.cpp는
// 이 선언을 자기 파일에서 forward-declare 해서 쓴다. 통합 시 정식
// 헤더 선언으로 옮기는 편이 낫다(최종 보고 참고).
namespace LegacyTransformPromotion
{
// 뼈 구파일 승격(트랙 E, E7-b) — PromoteLegacyTransform과 같은 이름공간에
// 두고 그 안에서 호출한다(아래). 별도 헤더 선언·별도 호출부를 늘리지
// 않은 이유: Prefab.cpp는 PromoteLegacyTransform 하나만 forward-declare
// 해서 쓰고(그쪽 파일은 이 슬라이스의 배정 밖이다), 이 함수를
// PromoteLegacyTransform 내부에서 위임 호출하면 Prefab.cpp를 전혀
// 건드리지 않고도 프리팹 인스턴스화 경로(Prefab.cpp:161)까지 자동으로
// 덮는다. 형제 함수로 나란히 두고 4곳 모두에서 따로 불렀다면 그중
// Prefab.cpp 1곳은 배정 밖 편집이 되어 배선 지시로 남겨야 했을 것이다.
//
// "구파일 여부" 판정은 PromoteLegacyTransform(m_transform 키 유무)과
// 다르다. E7-c 이후 신파일에는 m_gameObjectType이 없고 BoneComponent가
// 정본이다. 옛 Bone 키가 있으면서 m_components에 마커가 없는 경우만 승격한다:
// - 있다(신파일, 이 슬라이스 이후 재저장분) → 여기서는 아무 것도 하지
// 않는다. 아래 m_components 로드 루프(SceneManager.cpp 4곳·
// Prefab.cpp 1곳, 이 함수 호출부 바로 다음)가 정상적으로 채운다.
// 여기서 먼저 붙이면 Entity::AddComponent(Meta::Type&)의 중복
// 검사가 오브젝트마다 "이미 존재" 경고를 찍는다(Entity.cpp:196) —
// 흔한 정상 경로에 경고 로그가 쌓이는 것을 막는다.
// - 없다(구파일) → 여기서 붙여야 한다. 안 그러면
// Scene::UpdateModelRecursive의 Bone 분기가 HasComponent<BoneComponent>()로
// 판정하는 순간 이 오브젝트를 건너뛰어 애니메이션이 멈춘다.
void PromoteLegacyBone(Entity* obj, const Authoring::ReadNode& node)
{
// E7-c: 저장 타입은 더 이상 Entity 상태가 아니다. 옛 파일에 남은 키를
// 이 승격 순간에만 읽고, 신형 파일은 BoneComponent 블록 자체가 정본이다.
if (!obj || !node["m_gameObjectType"]
|| GameObjectType::Bone != static_cast<GameObjectType>(node["m_gameObjectType"].As<int>()))
return;
if (const Authoring::ReadNode componentsNode = node["m_components"])
{
for (const auto componentNode : componentsNode)
{
try
{
const Meta::Type* componentType = Meta::ExtractTypeFromYAML(componentNode);
if (componentType && componentType->typeID == type_guid(BoneComponent))
return; // 신파일 — 아래 m_components 로드 루프가 채운다.
}
catch (const std::exception&)
{
// 이 항목 파싱이 실패해도 여기서는 조용히 다음 항목을 본다 —
// 실제 로드(m_components 루프)가 같은 노드를 다시 만나
// 필요하면 그때 로그를 남긴다(SceneManager.cpp 위 catch 블록).
continue;
}
}
}
obj->AddComponent<BoneComponent>();
}
// obj->GetComponent<Transform>() 접근을 가정한다 — 레인 1의 최종
// API가 다르면 통합 담당이 이 한 줄만 맞추면 된다.
void PromoteLegacyTransform(Entity* obj, const Authoring::ReadNode& node)
{
if (!obj)
return;
// E7-b: 뼈 승격은 아래 m_transform 유무 판정과 무관하게 항상 시도한다
// — 신형 Transform으로 이미 재저장된 씬이라도(m_transform 키 없음)
// 뼈 마커는 그 판정과 독립으로 붙어야 한다(위 PromoteLegacyBone 주석).
PromoteLegacyBone(obj, node);
const Authoring::ReadNode legacyTransformNode = node["m_transform"];
if (!legacyTransformNode)
return; // 신파일 — 이미 m_components 블록에서 읽혔다.
Transform* transform = obj->GetComponent<Transform>();
if (!transform)
{
// S3: UI는 Transform을 갖지 않는다 — 구파일에 m_transform 키가
// 남아 있어도 승격할 대상이 없고, 승격해서도 안 된다(rect가 정본이다).
// 정상 경로이므로 조용히 넘긴다. 반대로 비-UI 오브젝트에서 여기 걸리면
// 자동 부착이 깨진 것인데, 그 유실은 verify-transform-roundtrip.ps1이
// 값 단위로 잡는다(그 검사를 이 슬라이스 착수 전에 먼저 세운 이유다).
return;
}
// position/rotation/scale만 승격한다. m_parentID는 여기서
// 건드리지 않는다 — Transform.h 주석(97-102줄)에 따르면
// Entity::SetParentIndex를 통해서만 바뀌어야 하는 값이고,
// 이 함수의 모든 호출부는 obj를 만들 때 이미 itNode/node의
// m_parentIndex로 부모를 확정한 뒤다. 게다가 Transform::SetParentID는
// private(friend GameObject만)라 여기서는 애초에 호출할 수 없다.
if (const auto positionNode = legacyTransformNode["position"])
{
math::vector4 value = transform->GetPositionValue();
Meta::Typed::ReadScalar(positionNode, value);
transform->SetPositionValue(value, TransformWriteReason::Reflection);
}
if (const auto rotationNode = legacyTransformNode["rotation"])
{
math::vector4 value = transform->GetRotationValue();
Meta::Typed::ReadScalar(rotationNode, value);
transform->SetRotationValue(value, TransformWriteReason::Reflection);
}
if (const auto scaleNode = legacyTransformNode["scale"])
{
math::vector4 value = transform->GetScaleValue();
Meta::Typed::ReadScalar(scaleNode, value);
transform->SetScaleValue(value, TransformWriteReason::Reflection);
}
}
}
SceneManager::~SceneManager() = default;
void SceneManager::SetGameStart(bool isStart)
{
if (!isStart)
{
SetGamePaused(false);
}
m_isGameStart = isStart;
// 재생 중에는 gen2 블로킹 수집을 억제한다(PHASE 9-6).
//
// 편집 중과 재생 중은 원하는 것이 반대다. 편집 중에는 메모리를 제때 돌려받는 편이
// 낫고(에셋을 계속 갈아 끼운다), 재생 중에는 프레임 예산이 우선이다 — 블로킹
// 수집 한 번이 프레임을 통째로 삼키면 그게 곧 히칭이다.
//
// 보장이 아니라 요청이라는 점은 알고 쓴다. 메모리 압박이 크면 런타임이 무시하고
// 수집한다. 그래서 이것만으로 히칭이 사라진다고 기대하지 않고, 9-7의 계측으로
// 실제 gen2 횟수가 줄었는지 확인한 뒤에 판단한다.
ClrHost::Get().SetManagedLatencyMode(isStart);
}
void SceneManager::SetGamePaused(bool isPaused)
{
if (!m_isGameStart && isPaused)
{
return;
}
const bool previousState = m_isGamePaused.exchange(isPaused);
if (previousState == isPaused)
{
return;
}
Time->ResetElapsedTime();
}
void SceneManager::ToggleGamePaused()
{
SetGamePaused(!IsGamePaused());
}
void SceneManager::ManagerInitialize()
{
RegisterReflectManual(); // CT4: 명시 메타 파일럿 4타입 — def에서 빠진 몫
ComponentFactorys->Initialize();
// 공용 작업자 풀. 소유는 층 1로 내렸고(WorkerPool.h) 수명만 여기서 잡는다.
WorkerPools->Startup();
m_inputActionManager = new InputActionManager();
InputActionManagers = m_inputActionManager;
InputActionManagers->LoadManager();
}
bool SceneManager::HasPendingSceneStructureChange() const
{
const bool needsPlayScene = m_isGameStart && !m_isEditorSceneLoaded;
const bool needsEditorScene = !m_isGameStart && m_isEditorSceneLoaded;
const bool needsActivation = m_sceneToActivate.load() != nullptr;
return needsPlayScene || needsEditorScene || needsActivation;
}
void SceneManager::ApplyPendingSceneStructureChange()
{
// 호출 지점이 렌더 정지 구간임을 전제로 한다(선언부 주석 참고).
// 씬 교체도 같은 이유로 여기서 처리한다. 활성 씬을 갈아끼우고 이전 씬을
// 파괴하는 작업이라 렌더가 도는 중에 하면 안 된다.
if (m_sceneToActivate.load())
{
BeforeAwakeSceneLoad();
}
if (m_isGameStart && !m_isEditorSceneLoaded)
{
auto activeScenePtr = m_activeScene.load();
if (!activeScenePtr) return;
PROFILE_CPU_BEGIN("BeginPlayTransaction");
BeginPlayTransaction();
PROFILE_CPU_END();
PROFILE_CPU_BEGIN("Reset");
activeScenePtr->Reset();
PROFILE_CPU_END();
m_isEditorSceneLoaded = true;
}
else if (!m_isGameStart && m_isEditorSceneLoaded)
{
PROFILE_CPU_BEGIN("EndPlayTransaction");
EndPlayTransaction();
PROFILE_CPU_END();
}
}
void SceneManager::Editor()
{
PROFILE_CPU_BEGIN("Editor");
// 재생/정지 전환은 여기서 하지 않는다. 렌더 스레드가 도는 중이기 때문이다.
// Dx11Main이 렌더 배리어 사이에서 ApplyPendingSceneStructureChange를 부른다.
if (!m_isGameStart)
{
auto activeScenePtr = m_activeScene.load();
if (!activeScenePtr) return;
// Sweep DDOL bucket for destroyed objects
std::erase_if(m_dontDestroyOnLoadObjects, [](Object* o){ return !o || o->IsDestroyMark(); });
//m_inputActionManager->ClearActionMaps(); //&&&&&TODO:게임스타트 한번만 초기화하고 다시들어가게
m_isInitialized = false; // Reset initialization state for editor scene
activeScenePtr->DrainPendingLifecycle();
}
PROFILE_CPU_END();
}
void SceneManager::Initialization()
{
if(!m_isInitialized)
{
m_isInitialized = true;
}
if (m_loadingSceneFuture.valid() &&
m_loadingSceneFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
{
try
{
// .get() retrieves the result. It will re-throw any exception caught in the async task.
Scene* loadedScene = m_loadingSceneFuture.get();
if (loadedScene)
{
// The scene is loaded, now activate it on the main thread.
ActivateScene(loadedScene);
}
}
catch (const std::exception& e)
{
Debug->LogError("Failed to activate loaded scene.");
// Handle loading failure
}
// The future is now invalid after .get(), so this block won't run again until a new scene is loaded.
}
if (!m_activeScene) return;
// 씬 교체(BeforeAwakeSceneLoad)는 여기서 하지 않는다.
// Dx11Main이 렌더 배리어 사이에서 처리하므로, 여기서는 교체가 끝난 씬을 깨우기만 한다.
// 옛 Awake→OnEnable→Start 3단은 뒤의 둘이 빈 함수라 사실상 드레인 하나였다
// (트랙 C · C4). 활성 전이는 Component::SetEnabled가 그 자리에서 처리하고,
// OnBeginSimulation은 이 드레인이 PendingSimulation까지 소진한다.
PROFILE_CPU_BEGIN("DrainPendingLifecycle");
m_activeScene.load()->DrainPendingLifecycle();
PROFILE_CPU_END();
}
void SceneManager::Physics(float deltaSecond)
{
if (!m_activeScene) return;
PROFILE_CPU_BEGIN("FixedUpdate");
m_activeScene.load()->FixedUpdate(deltaSecond);
PROFILE_CPU_END();
}
void SceneManager::InputEvents(float deltaSecond)
{
PROFILE_CPU_BEGIN("InputEvents");
InputEvent.Broadcast(deltaSecond);
PROFILE_CPU_END();
}
void SceneManager::GameLogic(float deltaSecond)
{
if (!m_activeScene) return;
PROFILE_CPU_BEGIN("Update");
m_activeScene.load()->Update(deltaSecond);
PROFILE_CPU_END();
PROFILE_CPU_BEGIN("YieldNull");
m_activeScene.load()->YieldNull();
PROFILE_CPU_END();
PROFILE_CPU_BEGIN("InternalAnimationUpdateEvent");
InternalAnimationUpdateEvent.Broadcast(deltaSecond);
PROFILE_CPU_END();
PROFILE_CPU_BEGIN("LateUpdate");
m_activeScene.load()->LateUpdate(deltaSecond);
PROFILE_CPU_END();
}
void SceneManager::SceneRendering(float deltaSecond)
{
SceneRenderingEvent.Broadcast(deltaSecond);
}
void SceneManager::OnDrawGizmos()
{
OnDrawGizmosEvent.Broadcast();
}
void SceneManager::GUIRendering()
{
GUIRenderingEvent.Broadcast();
}
void SceneManager::EndOfFrame()
{
PROFILE_CPU_BEGIN("EndOfFrame");
CoroutineManagers->yield_WaitForEndOfFrame();
endOfFrameEvent.Broadcast();
PROFILE_CPU_END();
// Sweep DDOL bucket for destroyed objects
std::erase_if(m_dontDestroyOnLoadObjects, [](Object* o){ return !o || o->IsDestroyMark(); });
}
void SceneManager::Pausing()
{
if (!m_activeScene) return;
m_activeScene.load()->UpdateRenderData();
}
void SceneManager::DisableOrEnable()
{
if (!m_activeScene) return;
m_activeScene.load()->EndFramePass();
}
void SceneManager::Decommissioning()
{
// 씬 수를 남긴다. 여기가 예상보다 많으면 목록에 중복이 들어간
// 것이고, 그것이 종료 시 더블 delete로 번진다(실제로 겪었다).
std::printf("[SHUTDOWN] Decommissioning 진입(씬 %zu)\n", m_scenes.size());
if (auto* renderScene = m_ActiveRenderScene.load())
{
renderScene->Finalize();
}
// DDOL 대상을 먼저 파괴 표시한다. Scene이 소유한 unique_ptr를 해제하기 전에
// 표시를 세워야 하며, 평상시 DDOL 목록은 비소유 포인터일 뿐이다.
for (Object* object : m_dontDestroyOnLoadObjects)
if (object) object->Destroy();
m_dontDestroyOnLoadObjects.clear();
for (auto& scene : m_scenes)
{
if (scene)
{
scene->AllDestroyMark();
scene->EndFramePass();
}
}
m_detachedDontDestroyOnLoadObjects.clear();
Memory::SafeDelete(m_inputActionManager);
WorkerPools->Shutdown();
PlayModeEvent.Clear();
InputEvent.Clear();
SceneRenderingEvent.Clear();
OnDrawGizmosEvent.Clear();
GUIRenderingEvent.Clear();
InternalAnimationUpdateEvent.Clear();
endOfFrameEvent.Clear();
sceneLoadedEvent.Clear();
sceneUnloadedEvent.Clear();
activeSceneChangedEvent.Clear();
newSceneCreatedEvent.Clear();
resourceTrimEvent.Clear();
m_activeScene = nullptr;
m_activeSceneIndex = 0;
for(auto& scene : m_scenes)
{
if (scene)
{
// EntityHandle은 씬 스코프다 — 이 씬이 죽으면 그 씬 소속으로 등록된
// 프리팹 인스턴스 항목도 함께 지운다(안 그러면 댕글링 Scene* — P2).
PrefabUtilitys->ForgetScene(scene);
delete scene;
}
}
}
void SceneManager::SetDecommissioning()
{
m_exitCommand = true;
}
Scene* SceneManager::CreateScene(std::string_view name)
{
resourceTrimEvent.Broadcast();
Scene* allocScene = Scene::CreateNewScene(name);
Scene* swapScene = nullptr;
if (!allocScene) return nullptr;
if (m_activeScene)
{
swapScene = m_activeScene.load();
sceneUnloadedEvent.Broadcast();
swapScene->AllDestroyMark();
swapScene->EndFramePass();
// 관리 측 그물은 파괴가 끝난 뒤에 던진다 — 근거는 ClrHost.h의 선언 주석 참고.
// sceneUnloadedEvent(위)는 파괴 '전'이라 이 자리에 쓸 수 없다.
ClrHost::Get().NotifySceneUnload();
std::erase_if(m_scenes,
[&](const auto& scene) { return scene == swapScene; });
// EntityHandle은 씬 스코프다 — 이 씬이 죽으면 그 씬 소속으로 등록된
// 프리팹 인스턴스 항목도 함께 지운다(안 그러면 댕글링 Scene* — P2).
PrefabUtilitys->ForgetScene(swapScene);
delete swapScene;
swapScene = nullptr;
m_activeScene = allocScene;
}
else
{
m_activeScene = allocScene;
}
m_scenes.push_back(allocScene);
m_activeSceneIndex = m_scenes.size() - 1;
allocScene->m_buildIndex = m_activeSceneIndex.load();
activeSceneChangedEvent.Broadcast();
newSceneCreatedEvent.Broadcast();
return allocScene;
}
Scene* SceneManager::SaveScene(std::string_view name)
{
std::string fileStem = name.data();
//std::string fileExtension = ".creator";
file::path saveSceneFileName = fileStem /*+ fileExtension*/;
// D3-b: 저작 텍스트는 LF로 쓴다. Windows의 텍스트 모드는 개행을 CRLF로 바꾸는데,
// 그러면 같은 내용을 저장할 때마다 개행이 뒤집혀 git 작업 트리가 흔들린다.
Authoring::WriteDocument sceneDocument;
try
{
m_activeScene.load()->OnBeforeSerialize();
m_activeScene.load()->m_Entities[0]->m_name = saveSceneFileName.stem().string();
sceneDocument = Meta::SerializeDocument(m_activeScene.load());
}
catch (const std::exception& e)
{
Debug->LogError(e.what());
return nullptr;
}
if (0 < m_dontDestroyOnLoadObjects.size())
{
Authoring::WriteNode dontDestroyOnLoadNode;
for (auto obj : m_dontDestroyOnLoadObjects)
{
if (!obj) continue;
auto* gameObject = dynamic_cast<Entity*>(obj);
if (gameObject)
{
if (!dontDestroyOnLoadNode)
{
dontDestroyOnLoadNode = sceneDocument.Root().Child(
"DontDestroyOnLoadObjects");
dontDestroyOnLoadNode.SetSequence();
}
Meta::SerializeInto(gameObject, Meta::TypeOf<Entity>(),
dontDestroyOnLoadNode.Append());
}
}
}
std::ofstream sceneFileOut(saveSceneFileName, std::ios::binary | std::ios::trunc);
sceneFileOut << sceneDocument.Dump();
sceneFileOut.close();
return m_activeScene;
}
Scene* SceneManager::LoadSceneImmediate(std::string_view name)
{
// D0(SerializationPlan): 이 함수 전체가 "씬 전환 1회"를 재는 자다. 하위 단계
// 합과 이 값의 차이가 곧 미귀속분이고, 그 차이를 숨기지 않는 것이 이 계측의 요점이다.
SERIALIZATION_PROFILE_SCOPE(SerializationProfile::Stage::SceneLoadTotal);
std::string loadSceneName = name.data();
try
{
Authoring::ParsedDocument sceneDocument;
{
// D0: 텍스트 → Node 트리 구축 구간만 따로 뗀다.
SERIALIZATION_PROFILE_SCOPE(SerializationProfile::Stage::SceneParse);
sceneDocument = ParseSceneDocument(loadSceneName);
}
const Authoring::ReadNode sceneNode = sceneDocument.Root();
Scene* swapScene{};
if (m_activeScene)
{
for(auto& object : m_dontDestroyOnLoadObjects)
{
auto* go = dynamic_cast<Entity*>(object);
if (go)
{
m_activeScene.load()->DetachEntityHierarchy(
go, m_detachedDontDestroyOnLoadObjects);
}
}
swapScene = m_activeScene.load();
sceneUnloadedEvent.Broadcast();
m_activeScene.load()->AllDestroyMark();
m_activeScene.load()->EndFramePass();
// 파괴 뒤에 던진다(ClrHost.h 선언 주석 참고).
ClrHost::Get().NotifySceneUnload();
m_activeScene = nullptr;
std::erase_if(m_scenes,
[&](const auto& scene) { return scene == swapScene; });
// EntityHandle은 씬 스코프다 — 이 씬이 죽으면 그 씬 소속으로 등록된
// 프리팹 인스턴스 항목도 함께 지운다(안 그러면 댕글링 Scene* — P2).
PrefabUtilitys->ForgetScene(swapScene);
delete swapScene;
}
file::path sceneName = name.data();
resourceTrimEvent.Broadcast();
m_activeScene = Scene::LoadScene(sceneName.stem().string());
if (const Authoring::ReadNode assetsBundleNode =
sceneNode["m_requiredLoadAssetsBundle"])
{
try
{
if (assetsBundleNode.IsNull())
{
Debug->LogError("AssetsBundle node is null.");
}
else
{
auto* assetBundle = &m_activeScene.load()->m_requiredLoadAssetsBundle;
Meta::Deserialize(assetBundle, assetsBundleNode);
//DataSystems->LoadAssetBundle(*assetBundle);
if (const Authoring::ReadNode assets = assetsBundleNode["assets"])
{
for (const Authoring::ReadNode asset : assets)
{
if(asset["assetTypeID"] && asset["assetName"])
{
AssetEntry entry{};
entry.assetTypeID = asset["assetTypeID"].As<int>();
entry.assetName = asset["assetName"].AsString();
if (!assetBundle->ContainsAsset(entry))
{
assetBundle->AddAsset(entry);
}
}
}
DataSystems->LoadAssetBundle(*assetBundle);
}
}
}
catch (...)
{
}
}
DataSystems->ClearRetainedAssets();
DataSystems->RetainAssets(m_dontDestroyOnLoadAssetsBundle);
DataSystems->RetainAssets(m_activeScene.load()->m_requiredLoadAssetsBundle);
// m_Entities와 DontDestroyOnLoadObjects 루프 둘 다 같은 씬(m_activeScene)의
// 슬롯을 할당하므로 배치 하나를 공유한다 — 파일 인덱스가 두 절 사이를
// 넘나들며 서로를 참조해도(예: DDOL이 일반 오브젝트를 부모로) 안전하다.
LoadIndexBatch loadBatch;
[[maybe_unused]] auto hierarchyTransaction =
m_activeScene.load()->BeginHierarchyBulkBuild();
for (const Authoring::ReadNode objNode : SerializedEntities(sceneNode))
{
try
{
const Meta::Type* type = Meta::ExtractTypeFromYAML(objNode);
if (!type)
{
Debug->LogError("Failed to extract type from YAML node.");
continue;
}
DesirealizeGameObject(type, Authoring::NodeViewAccess::Make(objNode), &loadBatch);
}
catch (const std::exception& e)
{
Debug->LogError(std::string("Failed to deserialize Entity: ") + e.what());
continue;
}
}
for (const Authoring::ReadNode objNode :
sceneNode["DontDestroyOnLoadObjects"])
{
try
{
const Meta::Type* type = Meta::ExtractTypeFromYAML(objNode);
if (!type)
{
Debug->LogError("Failed to extract type from YAML node.");
continue;
}
DesirealizeDontDestroyOnLoadObjects(m_activeScene.load(), type, Authoring::NodeViewAccess::Make(objNode), &loadBatch);
}
catch (const std::exception& e)
{
Debug->LogError(std::string("Failed to deserialize DontDestroyOnLoadObject: ") + e.what());
continue;
}
}
RemapLoadBatchIndices(m_activeScene.load(), loadBatch);
// 프리팹 인스턴스 재연결(SceneGraphRedesignPlan P2) — 리매핑 직후, m_Entities·
// DontDestroyOnLoadObjects 두 절이 공유하는 이 배치 전체를 한 번에 훑는다.
for (const auto& entry : loadBatch)
{
ReconnectPrefabInstance(m_activeScene.load(), entry.object);
}
hierarchyTransaction.Complete();
RebindEventDontDestroyOnLoadObjects(m_activeScene.load());
m_activeScene.load()->AllUpdateWorldMatrix(TransformSyncPoint::SceneLoad);
m_scenes.push_back(m_activeScene);
m_activeSceneIndex = m_scenes.size() - 1;
activeSceneChangedEvent.Broadcast();
sceneLoadedEvent.Broadcast();
// 여기 있던 "플레이어면 재생을 켠다" 분기는 PlayerMain으로 옮겼다(E3-6).
// 씬 로드가 곧 재생 시작이라는 것은 Player의 정책이지 씬 로더가 알아야 할
// 일이 아니다 — 로더가 실행 모드를 물어보는 대신 Player가 요청한다.
// "Scene loaded" 스모크 마커도 함께 갔다(Tools/build.ps1이 소비한다).
m_activeScene.load()->Reset();
}
catch (const std::exception& e)
{
Debug->LogError(e.what());
return nullptr;
}
return m_activeScene;
}
Scene* SceneManager::LoadScene(std::string_view name)
{
std::string loadSceneName = name.data();
Scene* scene{ nullptr };
try
{
Authoring::ParsedDocument sceneDocument;
{
// D0: 텍스트 → Node 트리 구축 구간만 따로 뗀다.
SERIALIZATION_PROFILE_SCOPE(SerializationProfile::Stage::SceneParse);
sceneDocument = ParseSceneDocument(loadSceneName);
}
const Authoring::ReadNode sceneNode = sceneDocument.Root();
file::path sceneName = name.data();
scene = Scene::LoadScene(sceneName.stem().string());
if (const Authoring::ReadNode assetsBundleNode = sceneNode["AssetsBundle"])
{
if (assetsBundleNode.IsNull())
{
Debug->LogError("AssetsBundle node is null.");
}
else
{
Meta::Deserialize(&scene->m_requiredLoadAssetsBundle, assetsBundleNode);
DataSystems->LoadAssetBundle(scene->m_requiredLoadAssetsBundle);
}
}
// ★ 두 루프가 서로 다른 씬을 타깃으로 한다 — m_Entities는 방금 만든
// `scene`으로, DontDestroyOnLoadObjects는 (아직 활성화 전인) m_activeScene으로
// 들어간다(이 함수의 기존 동작을 그대로 유지 — scene.load는 활성 씬을 바꾸지
// 않는다). 슬롯 인덱스 공간이 서로 다르므로 배치도 둘로 나눈다.
LoadIndexBatch sceneBatch;
LoadIndexBatch ddolBatch;
[[maybe_unused]] auto sceneHierarchyTransaction =
scene->BeginHierarchyBulkBuild();
[[maybe_unused]] auto ddolHierarchyTransaction =
m_activeScene.load()->BeginHierarchyBulkBuild();
for (const Authoring::ReadNode objNode : SerializedEntities(sceneNode))
{
const Meta::Type* type = Meta::ExtractTypeFromYAML(objNode);
if (!type)
{
Debug->LogError("Failed to extract type from YAML node.");
continue;
}
DesirealizeGameObject(scene, type, Authoring::NodeViewAccess::Make(objNode), &sceneBatch);
}
for (const Authoring::ReadNode objNode :
sceneNode["DontDestroyOnLoadObjects"])
{
const Meta::Type* type = Meta::ExtractTypeFromYAML(objNode);
if (!type)
{
Debug->LogError("Failed to extract type from YAML node.");
continue;
}
DesirealizeDontDestroyOnLoadObjects(m_activeScene.load(), type, Authoring::NodeViewAccess::Make(objNode), &ddolBatch);
}
RemapLoadBatchIndices(scene, sceneBatch);
RemapLoadBatchIndices(m_activeScene.load(), ddolBatch);
// 프리팹 인스턴스 재연결(SceneGraphRedesignPlan P2) — 두 배치가 서로 다른
// 씬을 타깃으로 하므로(위 주석 참고) 리매핑과 마찬가지로 따로 훑는다.
for (const auto& entry : sceneBatch)
{
ReconnectPrefabInstance(scene, entry.object);
}
for (const auto& entry : ddolBatch)
{
ReconnectPrefabInstance(m_activeScene.load(), entry.object);
}
sceneHierarchyTransaction.Complete();
ddolHierarchyTransaction.Complete();
scene->AllUpdateWorldMatrix(TransformSyncPoint::SceneLoad);
m_scenes.push_back(scene);
sceneLoadedEvent.Broadcast();
}
catch (const std::exception& e)
{
Debug->LogError(e.what());
return nullptr;
}
return scene;
}
void SceneManager::SaveSceneAsync(std::string_view name)
{
}
std::future<Scene*> SceneManager::LoadSceneAsync(std::string_view name)
{
return std::async(std::launch::async, [this, scenePath = std::string(name)]() -> Scene* {
try
{
// This code runs in a background thread.
Authoring::ParsedDocument sceneDocument = ParseSceneDocument(scenePath);
const Authoring::ReadNode sceneNode = sceneDocument.Root();
Scene* newScene = Scene::LoadScene(std::filesystem::path(scenePath).stem().string());
if (const Authoring::ReadNode assetsBundleNode =
sceneNode["m_requiredLoadAssetsBundle"])
{
try
{
if (!assetsBundleNode.IsNull())
{
auto* assetBundle = &newScene->m_requiredLoadAssetsBundle;
if (const Authoring::ReadNode assets = assetsBundleNode["assets"])
{
for (const Authoring::ReadNode asset : assets)
{
if (asset["assetTypeID"] && asset["assetName"])
{
AssetEntry entry{};
entry.assetTypeID = asset["assetTypeID"].As<int>();
entry.assetName = asset["assetName"].AsString();
if (!assetBundle->ContainsAsset(entry))
{
assetBundle->AddAsset(entry);
}
}
}
DataSystems->LoadAssetBundle(*assetBundle);
}
}
}
catch (...)
{
}
}
// 두 루프 모두 newScene을 타깃으로 하므로 배치를 공유한다.
LoadIndexBatch loadBatch;
[[maybe_unused]] auto hierarchyTransaction =
newScene->BeginHierarchyBulkBuild();
for (const Authoring::ReadNode objNode : SerializedEntities(sceneNode))
{
try
{
const Meta::Type* type = Meta::ExtractTypeFromYAML(objNode);
if (!type)
{
Debug->LogError("Failed to extract type from YAML node.");
continue;
}
DesirealizeGameObject(newScene, type, Authoring::NodeViewAccess::Make(objNode), &loadBatch);
}
catch (const std::exception& e)
{
Debug->LogError(std::string("Failed to deserialize Entity: ") + e.what());
continue;
}