脚本更新
This commit is contained in:
parent
498d202d6e
commit
84446d0060
File diff suppressed because it is too large
Load Diff
@ -133,6 +133,15 @@ void MetaCorePushGhostButtonStyle() {
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] const char* MetaCoreDebugPlayModeStateName(MetaCorePlayModeState state) {
|
||||
switch (state) {
|
||||
case MetaCorePlayModeState::Edit: return "Edit";
|
||||
case MetaCorePlayModeState::Playing: return "Playing";
|
||||
case MetaCorePlayModeState::Paused: return "Paused";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreToolbarToggle(const char* label, bool selected, ImVec2 size = ImVec2(0.0F, 24.0F)) {
|
||||
if (selected) {
|
||||
MetaCorePushFlatButtonStyle(GInfernuxAccent);
|
||||
@ -215,9 +224,23 @@ void MetaCoreDrawInfernuxToolbar(MetaCoreEditorContext& editorContext, const ImV
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
if (ImGui::Button(isPlaying ? "停止" : "播放", ImVec2(58.0F, 24.0F))) {
|
||||
const MetaCorePlayModeState beforeState = playModeService->GetState();
|
||||
std::cerr
|
||||
<< "[MetaCoreDebug][Toolbar] button="
|
||||
<< (isPlaying ? "Stop" : "Play")
|
||||
<< " before=" << MetaCoreDebugPlayModeStateName(beforeState)
|
||||
<< " sceneRevision=" << editorContext.GetScene().GetRevision()
|
||||
<< std::endl;
|
||||
const bool ok = isPlaying
|
||||
? playModeService->ExitPlayMode(editorContext)
|
||||
: playModeService->EnterPlayMode(editorContext);
|
||||
std::cerr
|
||||
<< "[MetaCoreDebug][Toolbar] button="
|
||||
<< (isPlaying ? "Stop" : "Play")
|
||||
<< " ok=" << (ok ? "true" : "false")
|
||||
<< " after=" << MetaCoreDebugPlayModeStateName(playModeService->GetState())
|
||||
<< " sceneRevision=" << editorContext.GetScene().GetRevision()
|
||||
<< std::endl;
|
||||
if (!ok) {
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "PlayMode", "播放模式状态切换失败。");
|
||||
}
|
||||
@ -233,7 +256,21 @@ void MetaCoreDrawInfernuxToolbar(MetaCoreEditorContext& editorContext, const ImV
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
if (ImGui::Button(isPaused ? "继续" : "暂停", ImVec2(64.0F, 24.0F))) {
|
||||
const MetaCorePlayModeState beforeState = playModeService->GetState();
|
||||
std::cerr
|
||||
<< "[MetaCoreDebug][Toolbar] button="
|
||||
<< (isPaused ? "Resume" : "Pause")
|
||||
<< " before=" << MetaCoreDebugPlayModeStateName(beforeState)
|
||||
<< " sceneRevision=" << editorContext.GetScene().GetRevision()
|
||||
<< std::endl;
|
||||
const bool ok = isPaused ? playModeService->Resume() : playModeService->Pause();
|
||||
std::cerr
|
||||
<< "[MetaCoreDebug][Toolbar] button="
|
||||
<< (isPaused ? "Resume" : "Pause")
|
||||
<< " ok=" << (ok ? "true" : "false")
|
||||
<< " after=" << MetaCoreDebugPlayModeStateName(playModeService->GetState())
|
||||
<< " sceneRevision=" << editorContext.GetScene().GetRevision()
|
||||
<< std::endl;
|
||||
if (!ok) {
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "PlayMode", "暂停状态切换失败。");
|
||||
}
|
||||
@ -249,7 +286,18 @@ void MetaCoreDrawInfernuxToolbar(MetaCoreEditorContext& editorContext, const ImV
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
if (ImGui::Button("单步", ImVec2(54.0F, 24.0F))) {
|
||||
if (!playModeService->Step(editorContext)) {
|
||||
std::cerr
|
||||
<< "[MetaCoreDebug][Toolbar] button=Step before="
|
||||
<< MetaCoreDebugPlayModeStateName(playModeService->GetState())
|
||||
<< " sceneRevision=" << editorContext.GetScene().GetRevision()
|
||||
<< std::endl;
|
||||
const bool ok = playModeService->Step(editorContext);
|
||||
std::cerr
|
||||
<< "[MetaCoreDebug][Toolbar] button=Step ok=" << (ok ? "true" : "false")
|
||||
<< " after=" << MetaCoreDebugPlayModeStateName(playModeService->GetState())
|
||||
<< " sceneRevision=" << editorContext.GetScene().GetRevision()
|
||||
<< std::endl;
|
||||
if (!ok) {
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "PlayMode", "单步只在暂停状态下可用。");
|
||||
}
|
||||
}
|
||||
@ -1136,12 +1184,9 @@ int MetaCoreEditorApp::Run() {
|
||||
|
||||
ImGui::Render();
|
||||
|
||||
// 1. 收集并更新场景数据(但不在这里渲染)
|
||||
MetaCoreSceneView sceneView = EditorContext_->GetCameraController().BuildSceneView();
|
||||
sceneView.SelectedObjectId = EditorContext_->GetSelectedObjectId();
|
||||
ViewportRenderer_.RenderSceneToViewport(Scene_, sceneView);
|
||||
|
||||
// 2. 统一在一个 Frame 内渲染离屏 3D 和上屏 UI
|
||||
// 视口面板在 DrawEditorFrame() 中完成本帧场景同步并把当前离屏纹理写入 ImGui。
|
||||
// 这里仅提交一次 Filament 渲染,避免状态按钮切换后同一帧二次 Resize/Sync
|
||||
// 让 ImGui draw data 采样到已释放的 FTexture。
|
||||
ViewportRenderer_.RenderAll();
|
||||
|
||||
Window_.EndFrame();
|
||||
@ -1422,6 +1467,23 @@ void MetaCoreEditorApp::DrawEditorFrame() {
|
||||
|
||||
// 7. 绘制画面 (应用 UV 翻转:Filament Y轴起点在底部,ImGui 在顶部)
|
||||
void* texPtr = ViewportRenderer_.GetFilamentTexturePointer();
|
||||
{
|
||||
static void* lastDebugTexturePointer = nullptr;
|
||||
static MetaCorePlayModeState lastDebugPlayModeState = MetaCorePlayModeState::Edit;
|
||||
const auto playModeService = ModuleRegistry_.ResolveService<MetaCoreIPlayModeService>();
|
||||
const MetaCorePlayModeState currentPlayModeState =
|
||||
playModeService != nullptr ? playModeService->GetState() : MetaCorePlayModeState::Edit;
|
||||
if (texPtr != lastDebugTexturePointer || currentPlayModeState != lastDebugPlayModeState) {
|
||||
std::cerr
|
||||
<< "[MetaCoreDebug][Viewport] ImGuiTexture pointer=" << texPtr
|
||||
<< " playState=" << MetaCoreDebugPlayModeStateName(currentPlayModeState)
|
||||
<< " viewport=" << viewportSize.x << "x" << viewportSize.y
|
||||
<< " sceneRevision=" << Scene_.GetRevision()
|
||||
<< std::endl;
|
||||
lastDebugTexturePointer = texPtr;
|
||||
lastDebugPlayModeState = currentPlayModeState;
|
||||
}
|
||||
}
|
||||
if (texPtr) {
|
||||
ImGui::Image(texPtr, viewportSize, ImVec2(0, 1), ImVec2(1, 0));
|
||||
}
|
||||
|
||||
@ -84,6 +84,25 @@ private:
|
||||
MetaCoreNearlyEqualVec3(lhs.Scale, rhs.Scale);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreScriptComponentEqual(const MetaCoreScriptComponent& lhs, const MetaCoreScriptComponent& rhs) {
|
||||
if (lhs.Instances.size() != rhs.Instances.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < lhs.Instances.size(); ++index) {
|
||||
const MetaCoreScriptInstanceData& lhsInstance = lhs.Instances[index];
|
||||
const MetaCoreScriptInstanceData& rhsInstance = rhs.Instances[index];
|
||||
if (lhsInstance.InstanceId != rhsInstance.InstanceId ||
|
||||
lhsInstance.ScriptTypeId != rhsInstance.ScriptTypeId ||
|
||||
lhsInstance.Enabled != rhsInstance.Enabled ||
|
||||
lhsInstance.FieldsJson != rhsInstance.FieldsJson) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreGameObjectEqual(const MetaCoreGameObjectData& lhs, const MetaCoreGameObjectData& rhs) {
|
||||
if (lhs.Id != rhs.Id || lhs.ParentId != rhs.ParentId || lhs.Name != rhs.Name || !MetaCoreTransformEqual(lhs.Transform, rhs.Transform)) {
|
||||
return false;
|
||||
@ -134,6 +153,13 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
if (lhs.Script.has_value() != rhs.Script.has_value()) {
|
||||
return false;
|
||||
}
|
||||
if (lhs.Script.has_value() && !MetaCoreScriptComponentEqual(*lhs.Script, *rhs.Script)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -49,6 +49,38 @@ void MetaCoreIEditorService::Shutdown(MetaCoreEditorModuleRegistry& moduleRegist
|
||||
(void)moduleRegistry;
|
||||
}
|
||||
|
||||
void MetaCoreIScriptBehaviour::OnPlayStart(MetaCoreScriptExecutionContext& context) {
|
||||
(void)context;
|
||||
}
|
||||
|
||||
void MetaCoreIScriptBehaviour::FixedUpdate(
|
||||
MetaCoreScriptExecutionContext& context,
|
||||
double fixedDeltaSeconds
|
||||
) {
|
||||
(void)context;
|
||||
(void)fixedDeltaSeconds;
|
||||
}
|
||||
|
||||
void MetaCoreIScriptBehaviour::Update(
|
||||
MetaCoreScriptExecutionContext& context,
|
||||
double deltaSeconds
|
||||
) {
|
||||
(void)context;
|
||||
(void)deltaSeconds;
|
||||
}
|
||||
|
||||
void MetaCoreIScriptBehaviour::LateUpdate(
|
||||
MetaCoreScriptExecutionContext& context,
|
||||
double deltaSeconds
|
||||
) {
|
||||
(void)context;
|
||||
(void)deltaSeconds;
|
||||
}
|
||||
|
||||
void MetaCoreIScriptBehaviour::OnPlayStop(MetaCoreScriptExecutionContext& context) {
|
||||
(void)context;
|
||||
}
|
||||
|
||||
void MetaCoreIPlayModeComponentSystem::OnPlayStart(MetaCoreEditorContext& editorContext) {
|
||||
(void)editorContext;
|
||||
}
|
||||
|
||||
@ -306,6 +306,57 @@ public:
|
||||
[[nodiscard]] virtual bool PasteComponent(std::string_view typeId, MetaCoreGameObject& gameObject) const = 0;
|
||||
};
|
||||
|
||||
enum class MetaCoreScriptFieldType {
|
||||
Bool = 0,
|
||||
Float,
|
||||
Vec3,
|
||||
String
|
||||
};
|
||||
|
||||
struct MetaCoreScriptFieldDefinition {
|
||||
std::string Name{};
|
||||
std::string DisplayName{};
|
||||
MetaCoreScriptFieldType Type = MetaCoreScriptFieldType::Float;
|
||||
bool BoolDefault = false;
|
||||
float FloatDefault = 0.0F;
|
||||
glm::vec3 Vec3Default{0.0F, 0.0F, 0.0F};
|
||||
std::string StringDefault{};
|
||||
};
|
||||
|
||||
struct MetaCoreScriptExecutionContext {
|
||||
MetaCoreEditorContext& EditorContext;
|
||||
MetaCoreGameObject GameObject;
|
||||
MetaCoreScriptInstanceData& Instance;
|
||||
};
|
||||
|
||||
class MetaCoreIScriptBehaviour {
|
||||
public:
|
||||
virtual ~MetaCoreIScriptBehaviour() = default;
|
||||
|
||||
virtual void OnPlayStart(MetaCoreScriptExecutionContext& context);
|
||||
virtual void FixedUpdate(MetaCoreScriptExecutionContext& context, double fixedDeltaSeconds);
|
||||
virtual void Update(MetaCoreScriptExecutionContext& context, double deltaSeconds);
|
||||
virtual void LateUpdate(MetaCoreScriptExecutionContext& context, double deltaSeconds);
|
||||
virtual void OnPlayStop(MetaCoreScriptExecutionContext& context);
|
||||
};
|
||||
|
||||
struct MetaCoreScriptDefinition {
|
||||
std::string TypeId{};
|
||||
std::string DisplayName{};
|
||||
std::vector<MetaCoreScriptFieldDefinition> Fields{};
|
||||
std::function<std::unique_ptr<MetaCoreIScriptBehaviour>()> Factory{};
|
||||
};
|
||||
|
||||
class MetaCoreIScriptRegistry : public MetaCoreIEditorService {
|
||||
public:
|
||||
virtual void RegisterScript(MetaCoreScriptDefinition definition) = 0;
|
||||
[[nodiscard]] virtual const MetaCoreScriptDefinition* FindScript(std::string_view typeId) const = 0;
|
||||
[[nodiscard]] virtual std::vector<MetaCoreScriptDefinition> GetScriptDefinitions() const = 0;
|
||||
[[nodiscard]] virtual std::unique_ptr<MetaCoreIScriptBehaviour> CreateBehaviour(std::string_view typeId) const = 0;
|
||||
[[nodiscard]] virtual std::string BuildDefaultFieldsJson(std::string_view typeId) const = 0;
|
||||
[[nodiscard]] virtual std::string EnsureFieldsJsonDefaults(std::string_view typeId, std::string_view fieldsJson) const = 0;
|
||||
};
|
||||
|
||||
enum class MetaCorePlayModeState {
|
||||
Edit = 0,
|
||||
Playing,
|
||||
|
||||
@ -49,6 +49,7 @@
|
||||
#include <unordered_set>
|
||||
#include <map>
|
||||
#include <string_view>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
@ -99,6 +100,17 @@ filament::Engine::Backend MetaCoreResolveFilamentBackend() {
|
||||
return filament::Engine::Backend::DEFAULT;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] std::string MetaCoreDebugPointer(T* pointer) {
|
||||
std::ostringstream stream;
|
||||
stream << static_cast<const void*>(pointer);
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
void MetaCoreFilamentDebugLog(const std::string& message) {
|
||||
std::cerr << "[MetaCoreDebug][Filament] " << message << std::endl;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class MetaCoreFilamentSceneBridge::MetaCoreFilamentSceneBridgeImpl {
|
||||
@ -189,6 +201,8 @@ public:
|
||||
.texture(filament::RenderTarget::AttachmentPoint::COLOR, FilamentTexture_)
|
||||
.texture(filament::RenderTarget::AttachmentPoint::DEPTH, DepthTexture_)
|
||||
.build(*Engine_);
|
||||
++RenderTargetGeneration_;
|
||||
DebugRenderTargetState("Initialize offscreen render target");
|
||||
|
||||
// 4. 设置到 View
|
||||
View_->setRenderTarget(RenderTarget_);
|
||||
@ -227,14 +241,24 @@ public:
|
||||
}
|
||||
if (Engine_) {
|
||||
std::cout << "FilamentSceneBridge: Shutting down..." << std::endl;
|
||||
DebugRenderTargetState("Shutdown begin");
|
||||
|
||||
// 1. 销毁所有加载的资产,切断其内部 Renderable 对克隆材质实例的占用引用
|
||||
std::cout << "[DEBUG] LoadedAssets_ size: " << LoadedAssets_.size() << std::endl;
|
||||
for (auto& [id, asset] : LoadedAssets_) {
|
||||
std::cout << "[DEBUG] Destroying asset for ID: " << id << ", asset pointer: " << asset << std::endl;
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Shutdown destroyAsset begin hostRootId=" + std::to_string(id) +
|
||||
" asset=" + MetaCoreDebugPointer(asset)
|
||||
);
|
||||
if (asset) {
|
||||
Engine_->flushAndWait();
|
||||
AssetLoader_->destroyAsset(asset);
|
||||
}
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Shutdown destroyAsset end hostRootId=" + std::to_string(id) +
|
||||
" asset=" + MetaCoreDebugPointer(asset)
|
||||
);
|
||||
std::cout << "[DEBUG] Destroyed asset for ID: " << id << std::endl;
|
||||
}
|
||||
LoadedAssets_.clear();
|
||||
@ -270,6 +294,11 @@ public:
|
||||
|
||||
// 3. 销毁所有克隆的材质实例,由于引用的实体都已被销毁,此时可安全释放
|
||||
for (auto& [id, mats] : AssetDuplicatedMaterials_) {
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Shutdown destroy duplicated materials hostRootId=" + std::to_string(id) +
|
||||
" count=" + std::to_string(mats.size())
|
||||
);
|
||||
Engine_->flushAndWait();
|
||||
for (auto* mat : mats) {
|
||||
if (mat) {
|
||||
Engine_->destroy(mat);
|
||||
@ -301,19 +330,30 @@ public:
|
||||
MaterialProvider_ = nullptr;
|
||||
}
|
||||
|
||||
DestroyDeferredRenderTargetResources(true);
|
||||
|
||||
// 销毁离屏渲染资源
|
||||
if (RenderTarget_) {
|
||||
DebugRenderTargetState("Shutdown destroy current render target begin");
|
||||
Engine_->flushAndWait();
|
||||
Engine_->destroy(RenderTarget_);
|
||||
RenderTarget_ = nullptr;
|
||||
}
|
||||
if (FilamentTexture_) {
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Shutdown destroy current colorTexture=" + MetaCoreDebugPointer(FilamentTexture_)
|
||||
);
|
||||
Engine_->destroy(FilamentTexture_);
|
||||
FilamentTexture_ = nullptr;
|
||||
}
|
||||
if (DepthTexture_) {
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Shutdown destroy current depthTexture=" + MetaCoreDebugPointer(DepthTexture_)
|
||||
);
|
||||
Engine_->destroy(DepthTexture_);
|
||||
DepthTexture_ = nullptr;
|
||||
}
|
||||
DebugRenderTargetState("Shutdown after current render target destroy");
|
||||
if (GLTextureId_) {
|
||||
glDeleteTextures(1, &GLTextureId_);
|
||||
GLTextureId_ = 0;
|
||||
@ -530,6 +570,18 @@ public:
|
||||
|
||||
if (!activeHostRootIds.contains(hostRootId) || invalidated) {
|
||||
std::cout << "FilamentSceneBridge: Unloading deleted asset: " << hostRootId << std::endl;
|
||||
const size_t assetEntityCount = asset != nullptr ? asset->getEntityCount() : 0;
|
||||
const auto materialIt = AssetDuplicatedMaterials_.find(hostRootId);
|
||||
const size_t duplicatedMaterialCount =
|
||||
materialIt != AssetDuplicatedMaterials_.end() ? materialIt->second.size() : 0;
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Unload begin hostRootId=" + std::to_string(hostRootId) +
|
||||
" asset=" + MetaCoreDebugPointer(asset) +
|
||||
" active=" + (activeHostRootIds.contains(hostRootId) ? std::string("true") : std::string("false")) +
|
||||
" invalidated=" + (invalidated ? std::string("true") : std::string("false")) +
|
||||
" entities=" + std::to_string(assetEntityCount) +
|
||||
" duplicatedMaterials=" + std::to_string(duplicatedMaterialCount)
|
||||
);
|
||||
|
||||
// 从场景中移除该资产关联的所有实体
|
||||
if (Scene_ && asset) {
|
||||
@ -555,9 +607,27 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// 等待后台渲染线程消费完上一帧命令,再销毁 GLTF 资产持有的纹理/材质。
|
||||
// 脚本播放会每帧推进 Transform,暂停/热更新等操作可能和渲染同步落在相邻帧,
|
||||
// 不等待会让 OpenGL descriptor 绑定已释放的 FTexture。
|
||||
if (Engine_) {
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Unload flush before destroyAsset hostRootId=" + std::to_string(hostRootId)
|
||||
);
|
||||
Engine_->flushAndWait();
|
||||
}
|
||||
|
||||
// 销毁资产本身
|
||||
if (asset) {
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Unload destroyAsset begin hostRootId=" + std::to_string(hostRootId) +
|
||||
" asset=" + MetaCoreDebugPointer(asset)
|
||||
);
|
||||
AssetLoader_->destroyAsset(asset);
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Unload destroyAsset end hostRootId=" + std::to_string(hostRootId) +
|
||||
" asset=" + MetaCoreDebugPointer(asset)
|
||||
);
|
||||
}
|
||||
|
||||
// 从 ObjectToFilamentEntity_ 和 ObjectWorldMatrices_ 中清除所有指向该 asset 的映射
|
||||
@ -577,17 +647,32 @@ public:
|
||||
// 销毁克隆的材质
|
||||
auto matIt = AssetDuplicatedMaterials_.find(hostRootId);
|
||||
if (matIt != AssetDuplicatedMaterials_.end()) {
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Unload destroy duplicated materials begin hostRootId=" + std::to_string(hostRootId) +
|
||||
" count=" + std::to_string(matIt->second.size())
|
||||
);
|
||||
if (Engine_) {
|
||||
Engine_->flushAndWait();
|
||||
}
|
||||
for (auto* mat : matIt->second) {
|
||||
if (mat) {
|
||||
Engine_->destroy(mat);
|
||||
}
|
||||
}
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Unload destroy duplicated materials end hostRootId=" + std::to_string(hostRootId)
|
||||
);
|
||||
AssetDuplicatedMaterials_.erase(matIt);
|
||||
}
|
||||
|
||||
// 从 LoadedAssets_ 中移除
|
||||
HostRootSourceModelGuids_.erase(hostRootId);
|
||||
it = LoadedAssets_.erase(it);
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Unload end hostRootId=" + std::to_string(hostRootId) +
|
||||
" loadedAssets=" + std::to_string(LoadedAssets_.size()) +
|
||||
" objectMappings=" + std::to_string(ObjectToFilamentEntity_.size())
|
||||
);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
@ -630,6 +715,12 @@ public:
|
||||
|
||||
// 加载新模型,以顶级根宿主 hostRootId 进行注册存储
|
||||
std::cout << "FilamentSceneBridge: Loading GLTF: " << absolutePath << " (Host: " << hostRootName << ")" << std::endl;
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Load begin hostRootId=" + std::to_string(hostRootId) +
|
||||
" host=\"" + hostRootName + "\"" +
|
||||
" modelPath=\"" + modelPath + "\"" +
|
||||
" loadedAssets=" + std::to_string(LoadedAssets_.size())
|
||||
);
|
||||
|
||||
std::ifstream file(absolutePath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
@ -648,6 +739,11 @@ public:
|
||||
std::cerr << "Failed to create asset!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Load createAsset hostRootId=" + std::to_string(hostRootId) +
|
||||
" asset=" + MetaCoreDebugPointer(asset) +
|
||||
" entities=" + std::to_string(asset->getEntityCount())
|
||||
);
|
||||
|
||||
// 加载贴图等资源
|
||||
filament::gltfio::ResourceLoader resourceLoader({ Engine_ });
|
||||
@ -692,6 +788,10 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Load duplicated materials hostRootId=" + std::to_string(hostRootId) +
|
||||
" count=" + std::to_string(dupList.size())
|
||||
);
|
||||
}
|
||||
|
||||
if (scene != nullptr) {
|
||||
@ -975,6 +1075,23 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
++RenderFrameIndex_;
|
||||
if (!PendingRenderTargetResources_.empty()) {
|
||||
DebugRenderTargetState("RenderAll before beginFrame with pending destroy");
|
||||
}
|
||||
|
||||
// ImGui draw data -> Filament renderable 的同步会修改 Renderable、VertexBuffer、
|
||||
// MaterialInstance 参数和 descriptor 绑定。必须在 beginFrame/render pass 之前完成,
|
||||
// 否则 Filament 会警告 descriptor set 在 begin/endRenderPass 中被修改,并可能在
|
||||
// 后续句柄校验中触发 FTexture use-after-free/Postcondition 崩溃。
|
||||
const bool hasUiPass = UIView_ != nullptr && ImGuiHelper_ != nullptr;
|
||||
if (hasUiPass) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuiHelper_->setDisplaySize(io.DisplaySize.x, io.DisplaySize.y);
|
||||
UIView_->setViewport({0, 0, static_cast<uint32_t>(io.DisplaySize.x), static_cast<uint32_t>(io.DisplaySize.y)});
|
||||
ImGuiHelper_->processImGuiCommands(ImGui::GetDrawData(), io);
|
||||
}
|
||||
|
||||
if (Renderer_->beginFrame(SwapChain_)) {
|
||||
if (RenderTarget_) {
|
||||
// 1. 渲染 3D 离屏视口 (输出到 RenderTarget)
|
||||
@ -985,18 +1102,11 @@ public:
|
||||
|
||||
Renderer_->render(View_);
|
||||
|
||||
if (UIView_ && ImGuiHelper_) {
|
||||
if (hasUiPass) {
|
||||
// 2. 准备并渲染 UI 视口 (输出到 SwapChain)
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuiHelper_->setDisplaySize(io.DisplaySize.x, io.DisplaySize.y);
|
||||
UIView_->setViewport({0, 0, static_cast<uint32_t>(io.DisplaySize.x), static_cast<uint32_t>(io.DisplaySize.y)});
|
||||
|
||||
options.clearColor = { 0.11f, 0.12f, 0.14f, 1.0f }; // 灰色
|
||||
options.clear = true;
|
||||
Renderer_->setClearOptions(options);
|
||||
|
||||
ImGuiHelper_->processImGuiCommands(ImGui::GetDrawData(), io);
|
||||
|
||||
Renderer_->render(UIView_);
|
||||
}
|
||||
} else {
|
||||
@ -1011,11 +1121,9 @@ public:
|
||||
|
||||
Renderer_->endFrame();
|
||||
|
||||
// 在帧结束、指令提交后,安全地销毁上一帧遗留的旧纹理
|
||||
for (auto* tex : OldTextures_) {
|
||||
Engine_->destroy(tex);
|
||||
}
|
||||
OldTextures_.clear();
|
||||
DestroyDeferredRenderTargetResources(true);
|
||||
} else if (!PendingRenderTargetResources_.empty()) {
|
||||
DebugRenderTargetState("RenderAll beginFrame failed with pending destroy");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1027,6 +1135,10 @@ public:
|
||||
if (width <= 0 || height <= 0) return;
|
||||
|
||||
if (!RenderTarget_) {
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Resize without render target width=" + std::to_string(width) +
|
||||
" height=" + std::to_string(height)
|
||||
);
|
||||
View_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
|
||||
return;
|
||||
}
|
||||
@ -1039,13 +1151,15 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
if (RenderTarget_) Engine_->destroy(RenderTarget_);
|
||||
if (DepthTexture_) Engine_->destroy(DepthTexture_);
|
||||
|
||||
// 【关键修复】:推迟销毁旧纹理,避免当前帧 ImGui 的 DrawList 还在引用它导致崩溃。
|
||||
if (FilamentTexture_) {
|
||||
OldTextures_.push_back(FilamentTexture_);
|
||||
}
|
||||
MetaCoreFilamentDebugLog(
|
||||
"Resize recreate begin width=" + std::to_string(width) +
|
||||
" height=" + std::to_string(height) +
|
||||
" oldWidth=" + (FilamentTexture_ != nullptr ? std::to_string(FilamentTexture_->getWidth()) : std::string("null")) +
|
||||
" oldHeight=" + (FilamentTexture_ != nullptr ? std::to_string(FilamentTexture_->getHeight()) : std::string("null"))
|
||||
);
|
||||
DebugRenderTargetState("Resize before queue current resources");
|
||||
QueueCurrentRenderTargetResourcesForDestroy();
|
||||
DestroyDeferredRenderTargetResources(true);
|
||||
|
||||
FilamentTexture_ = filament::Texture::Builder()
|
||||
.width(static_cast<uint32_t>(width))
|
||||
@ -1065,15 +1179,67 @@ public:
|
||||
.texture(filament::RenderTarget::AttachmentPoint::COLOR, FilamentTexture_)
|
||||
.texture(filament::RenderTarget::AttachmentPoint::DEPTH, DepthTexture_)
|
||||
.build(*Engine_);
|
||||
++RenderTargetGeneration_;
|
||||
|
||||
View_->setRenderTarget(RenderTarget_);
|
||||
View_->setViewport({0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
|
||||
DebugRenderTargetState("Resize after recreate resources");
|
||||
}
|
||||
|
||||
void* GetFilamentTexturePointer() const {
|
||||
return FilamentTexture_;
|
||||
}
|
||||
|
||||
void QueueCurrentRenderTargetResourcesForDestroy() {
|
||||
if (RenderTarget_ == nullptr && FilamentTexture_ == nullptr && DepthTexture_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
PendingRenderTargetResources_.push_back(PendingRenderTargetResource{
|
||||
RenderTarget_,
|
||||
FilamentTexture_,
|
||||
DepthTexture_,
|
||||
RenderTargetGeneration_
|
||||
});
|
||||
DebugRenderTargetState("Queue current render target resources for destroy");
|
||||
RenderTarget_ = nullptr;
|
||||
FilamentTexture_ = nullptr;
|
||||
DepthTexture_ = nullptr;
|
||||
}
|
||||
|
||||
void DestroyDeferredRenderTargetResources(bool waitForBackend) {
|
||||
if (PendingRenderTargetResources_.empty() || Engine_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (waitForBackend) {
|
||||
DebugRenderTargetState("Destroy deferred resources flush begin");
|
||||
Engine_->flushAndWait();
|
||||
DebugRenderTargetState("Destroy deferred resources flush end");
|
||||
}
|
||||
|
||||
for (PendingRenderTargetResource& resource : PendingRenderTargetResources_) {
|
||||
std::ostringstream stream;
|
||||
stream
|
||||
<< "Destroy deferred resource generation=" << resource.Generation
|
||||
<< " renderTarget=" << MetaCoreDebugPointer(resource.RenderTarget)
|
||||
<< " colorTexture=" << MetaCoreDebugPointer(resource.ColorTexture)
|
||||
<< " depthTexture=" << MetaCoreDebugPointer(resource.DepthTexture);
|
||||
MetaCoreFilamentDebugLog(stream.str());
|
||||
if (resource.RenderTarget != nullptr) {
|
||||
Engine_->destroy(resource.RenderTarget);
|
||||
}
|
||||
if (resource.ColorTexture != nullptr) {
|
||||
Engine_->destroy(resource.ColorTexture);
|
||||
}
|
||||
if (resource.DepthTexture != nullptr) {
|
||||
Engine_->destroy(resource.DepthTexture);
|
||||
}
|
||||
}
|
||||
PendingRenderTargetResources_.clear();
|
||||
DebugRenderTargetState("Destroy deferred resources end");
|
||||
}
|
||||
|
||||
bool TryGetObjectWorldMatrix(MetaCoreId objectId, glm::mat4& worldMatrix) const {
|
||||
const auto worldIt = ObjectWorldMatrices_.find(objectId);
|
||||
if (worldIt == ObjectWorldMatrices_.end()) {
|
||||
@ -1338,6 +1504,21 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void DebugRenderTargetState(const char* event) const {
|
||||
std::ostringstream stream;
|
||||
stream
|
||||
<< event
|
||||
<< " frame=" << RenderFrameIndex_
|
||||
<< " generation=" << RenderTargetGeneration_
|
||||
<< " renderTarget=" << MetaCoreDebugPointer(RenderTarget_)
|
||||
<< " colorTexture=" << MetaCoreDebugPointer(FilamentTexture_)
|
||||
<< " depthTexture=" << MetaCoreDebugPointer(DepthTexture_)
|
||||
<< " pending=" << PendingRenderTargetResources_.size()
|
||||
<< " loadedAssets=" << LoadedAssets_.size()
|
||||
<< " objectMappings=" << ObjectToFilamentEntity_.size();
|
||||
MetaCoreFilamentDebugLog(stream.str());
|
||||
}
|
||||
|
||||
filament::Engine* Engine_ = nullptr;
|
||||
filament::SwapChain* SwapChain_ = nullptr;
|
||||
filament::Renderer* Renderer_ = nullptr;
|
||||
@ -1364,10 +1545,18 @@ private:
|
||||
MetaCoreImGuiHelper* ImGuiHelper_ = nullptr;
|
||||
|
||||
GLuint GLTextureId_ = 0;
|
||||
struct PendingRenderTargetResource {
|
||||
filament::RenderTarget* RenderTarget = nullptr;
|
||||
filament::Texture* ColorTexture = nullptr;
|
||||
filament::Texture* DepthTexture = nullptr;
|
||||
std::uint64_t Generation = 0;
|
||||
};
|
||||
filament::Texture* FilamentTexture_ = nullptr;
|
||||
filament::Texture* DepthTexture_ = nullptr;
|
||||
filament::RenderTarget* RenderTarget_ = nullptr;
|
||||
std::vector<filament::Texture*> OldTextures_;
|
||||
std::vector<PendingRenderTargetResource> PendingRenderTargetResources_;
|
||||
std::uint64_t RenderFrameIndex_ = 0;
|
||||
std::uint64_t RenderTargetGeneration_ = 0;
|
||||
utils::Entity Light_;
|
||||
|
||||
std::filesystem::path ProjectRootPath_{};
|
||||
|
||||
@ -90,7 +90,9 @@ MetaCoreImGuiHelper::MetaCoreImGuiHelper(Engine* engine, filament::View* view, c
|
||||
|
||||
void MetaCoreImGuiHelper::createAtlasTexture(Engine* engine) {
|
||||
if (mTexture) {
|
||||
mImGuiTextures.erase(mTexture);
|
||||
engine->destroy(mTexture);
|
||||
mTexture = nullptr;
|
||||
}
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
@ -132,25 +134,51 @@ MetaCoreImGuiHelper::~MetaCoreImGuiHelper() {
|
||||
ImGui::SetCurrentContext(mImGuiContext);
|
||||
ImGui::GetIO().BackendFlags &= ~ImGuiBackendFlags_RendererHasTextures;
|
||||
|
||||
mEngine->destroy(mScene);
|
||||
mEngine->destroy(mRenderable);
|
||||
auto& rcm = mEngine->getRenderableManager();
|
||||
if (rcm.getInstance(mRenderable)) {
|
||||
rcm.destroy(mRenderable);
|
||||
}
|
||||
mRenderableBuilt = false;
|
||||
if (mScene) {
|
||||
mScene->remove(mRenderable);
|
||||
}
|
||||
mEngine->destroyCameraComponent(mCameraEntity);
|
||||
|
||||
for (auto& mi : mMaterial2dInstances) {
|
||||
mEngine->destroy(mi);
|
||||
if (mi != nullptr) {
|
||||
mEngine->destroy(mi);
|
||||
}
|
||||
}
|
||||
if (mMaterial2d != nullptr) {
|
||||
mEngine->destroy(mMaterial2d);
|
||||
}
|
||||
if (mTexture != nullptr) {
|
||||
mImGuiTextures.erase(mTexture);
|
||||
mEngine->destroy(mTexture);
|
||||
mTexture = nullptr;
|
||||
}
|
||||
mEngine->destroy(mMaterial2d);
|
||||
mEngine->destroy(mTexture);
|
||||
|
||||
for (auto& vb : mVertexBuffers) {
|
||||
mEngine->destroy(vb);
|
||||
if (vb != nullptr) {
|
||||
mEngine->destroy(vb);
|
||||
}
|
||||
}
|
||||
for (auto& ib : mIndexBuffers) {
|
||||
mEngine->destroy(ib);
|
||||
if (ib != nullptr) {
|
||||
mEngine->destroy(ib);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto itextures: mImGuiTextures) {
|
||||
mEngine->destroy(itextures);
|
||||
if (itextures != nullptr) {
|
||||
mEngine->destroy(itextures);
|
||||
}
|
||||
}
|
||||
mImGuiTextures.clear();
|
||||
|
||||
if (mScene != nullptr) {
|
||||
mEngine->destroy(mScene);
|
||||
mScene = nullptr;
|
||||
}
|
||||
|
||||
EntityManager& em = utils::EntityManager::get();
|
||||
@ -218,13 +246,25 @@ void MetaCoreImGuiHelper::processImGuiCommands(ImDrawData* commands, const ImGui
|
||||
size_t nPrims = 0;
|
||||
for (int cmdListIndex = 0; cmdListIndex < commands->CmdListsCount; cmdListIndex++) {
|
||||
const ImDrawList* cmds = commands->CmdLists[cmdListIndex];
|
||||
nPrims += cmds->CmdBuffer.size();
|
||||
for (const auto& pcmd : cmds->CmdBuffer) {
|
||||
if (pcmd.UserCallback == nullptr && pcmd.ElemCount > 0) {
|
||||
++nPrims;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rcm.getInstance(mRenderable)) {
|
||||
rcm.destroy(mRenderable);
|
||||
}
|
||||
mRenderableBuilt = false;
|
||||
|
||||
if (nPrims == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto rbuilder = RenderableManager::Builder(nPrims);
|
||||
rbuilder.boundingBox({{ 0, 0, 0 }, { 10000, 10000, 10000 }}).culling(false);
|
||||
|
||||
rcm.destroy(mRenderable);
|
||||
int bufferIndex = 0;
|
||||
int primIndex = 0;
|
||||
int material2dIndex = 0;
|
||||
@ -239,6 +279,9 @@ void MetaCoreImGuiHelper::processImGuiCommands(ImDrawData* commands, const ImGui
|
||||
if (pcmd.UserCallback) {
|
||||
pcmd.UserCallback(cmds, &pcmd);
|
||||
} else {
|
||||
if (pcmd.ElemCount == 0) {
|
||||
continue;
|
||||
}
|
||||
auto texture = (Texture const*)pcmd.GetTexID();
|
||||
MaterialInstance* materialInstance;
|
||||
|
||||
@ -279,8 +322,9 @@ void MetaCoreImGuiHelper::processImGuiCommands(ImDrawData* commands, const ImGui
|
||||
}
|
||||
bufferIndex++;
|
||||
}
|
||||
if (commands->CmdListsCount > 0) {
|
||||
if (primIndex > 0) {
|
||||
rbuilder.build(*mEngine, mRenderable);
|
||||
mRenderableBuilt = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -370,9 +414,19 @@ void MetaCoreImGuiHelper::updateTexture(ImTextureData* textureData) {
|
||||
}
|
||||
|
||||
if (textureData->Status == ImTextureStatus_WantCreate) {
|
||||
if (mTexture != nullptr) {
|
||||
mEngine->destroy(mTexture);
|
||||
mTexture = nullptr;
|
||||
auto* oldTexture = static_cast<Texture*>(textureData->BackendUserData);
|
||||
if (oldTexture == nullptr && textureData->TexID != ImTextureID_Invalid) {
|
||||
oldTexture = reinterpret_cast<Texture*>(static_cast<std::uintptr_t>(textureData->TexID));
|
||||
}
|
||||
if (oldTexture != nullptr) {
|
||||
const bool isFontTexture = oldTexture == mTexture;
|
||||
const bool isOwnedTexture = isFontTexture || mImGuiTextures.erase(oldTexture) > 0;
|
||||
if (isOwnedTexture) {
|
||||
mEngine->destroy(oldTexture);
|
||||
}
|
||||
if (isFontTexture) {
|
||||
mTexture = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Texture::PixelBufferDescriptor pb(
|
||||
@ -381,7 +435,7 @@ void MetaCoreImGuiHelper::updateTexture(ImTextureData* textureData) {
|
||||
Texture::Format::RGBA,
|
||||
Texture::Type::UBYTE);
|
||||
|
||||
mTexture = Texture::Builder()
|
||||
Texture* createdTexture = Texture::Builder()
|
||||
.width(static_cast<uint32_t>(textureData->Width))
|
||||
.height(static_cast<uint32_t>(textureData->Height))
|
||||
.levels(static_cast<uint8_t>(1))
|
||||
@ -389,9 +443,25 @@ void MetaCoreImGuiHelper::updateTexture(ImTextureData* textureData) {
|
||||
.sampler(Texture::Sampler::SAMPLER_2D)
|
||||
.build(*mEngine);
|
||||
|
||||
mTexture->setImage(*mEngine, 0, std::move(pb));
|
||||
textureData->BackendUserData = mTexture;
|
||||
textureData->SetTexID(static_cast<ImTextureID>(reinterpret_cast<std::uintptr_t>(mTexture)));
|
||||
createdTexture->setImage(*mEngine, 0, std::move(pb));
|
||||
Texture* texture = createdTexture;
|
||||
#if IMGUI_VERSION_NUM >= 19200
|
||||
const bool isFontAtlasTexture =
|
||||
ImGui::GetIO().Fonts != nullptr &&
|
||||
ImGui::GetIO().Fonts->TexData == textureData;
|
||||
if (isFontAtlasTexture) {
|
||||
mTexture = createdTexture;
|
||||
if (mMaterial2d) {
|
||||
mMaterial2d->setDefaultParameter("albedo", mTexture, mSampler);
|
||||
}
|
||||
} else {
|
||||
mImGuiTextures.insert(createdTexture);
|
||||
}
|
||||
#else
|
||||
mTexture = createdTexture;
|
||||
#endif
|
||||
textureData->BackendUserData = texture;
|
||||
textureData->SetTexID(static_cast<ImTextureID>(reinterpret_cast<std::uintptr_t>(texture)));
|
||||
textureData->SetStatus(ImTextureStatus_OK);
|
||||
return;
|
||||
}
|
||||
@ -432,9 +502,16 @@ void MetaCoreImGuiHelper::updateTexture(ImTextureData* textureData) {
|
||||
|
||||
if (textureData->Status == ImTextureStatus_WantDestroy && textureData->UnusedFrames > 0) {
|
||||
auto* texture = static_cast<Texture*>(textureData->BackendUserData);
|
||||
if (texture != nullptr && texture != mTexture) {
|
||||
const bool isFontTexture = texture != nullptr && texture == mTexture;
|
||||
const bool isOwnedTexture =
|
||||
texture != nullptr &&
|
||||
(isFontTexture || mImGuiTextures.erase(texture) > 0);
|
||||
if (isOwnedTexture) {
|
||||
mEngine->destroy(texture);
|
||||
}
|
||||
if (isFontTexture) {
|
||||
mTexture = nullptr;
|
||||
}
|
||||
textureData->BackendUserData = nullptr;
|
||||
textureData->SetTexID(ImTextureID_Invalid);
|
||||
textureData->SetStatus(ImTextureStatus_Destroyed);
|
||||
|
||||
@ -66,6 +66,7 @@ private:
|
||||
filament::TextureSampler mSampler;
|
||||
bool mFlipVertical = false;
|
||||
bool mOwnsImGuiContext = false;
|
||||
bool mRenderableBuilt = false;
|
||||
std::filesystem::path mSettingsPath;
|
||||
std::unordered_set<filament::Texture*> mImGuiTextures;
|
||||
};
|
||||
|
||||
@ -271,6 +271,9 @@ std::vector<MetaCoreId> MetaCoreScene::DuplicateGameObjects(const std::vector<Me
|
||||
|
||||
if (sourceObject.HasComponent<MetaCoreRotatorComponent>())
|
||||
clonedObject.AddComponent<MetaCoreRotatorComponent>(sourceObject.GetComponent<MetaCoreRotatorComponent>());
|
||||
|
||||
if (sourceObject.HasComponent<MetaCoreScriptComponent>())
|
||||
clonedObject.AddComponent<MetaCoreScriptComponent>(sourceObject.GetComponent<MetaCoreScriptComponent>());
|
||||
|
||||
if (sourceObject.HasComponent<MetaCorePrefabInstanceMetadata>())
|
||||
clonedObject.AddComponent<MetaCorePrefabInstanceMetadata>(sourceObject.GetComponent<MetaCorePrefabInstanceMetadata>());
|
||||
@ -421,6 +424,8 @@ MetaCoreSceneSnapshot MetaCoreScene::CaptureSnapshot() const {
|
||||
data.Light = obj.GetComponent<MetaCoreLightComponent>();
|
||||
if (obj.HasComponent<MetaCoreRotatorComponent>())
|
||||
data.Rotator = obj.GetComponent<MetaCoreRotatorComponent>();
|
||||
if (obj.HasComponent<MetaCoreScriptComponent>())
|
||||
data.Script = obj.GetComponent<MetaCoreScriptComponent>();
|
||||
if (obj.HasComponent<MetaCorePrefabInstanceMetadata>())
|
||||
data.PrefabInstance = obj.GetComponent<MetaCorePrefabInstanceMetadata>();
|
||||
if (obj.HasComponent<MetaCoreModelRootTag>())
|
||||
@ -449,6 +454,8 @@ void MetaCoreScene::RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot) {
|
||||
obj.AddComponent<MetaCoreLightComponent>(data.Light.value());
|
||||
if (data.Rotator.has_value())
|
||||
obj.AddComponent<MetaCoreRotatorComponent>(data.Rotator.value());
|
||||
if (data.Script.has_value())
|
||||
obj.AddComponent<MetaCoreScriptComponent>(data.Script.value());
|
||||
if (data.PrefabInstance.has_value())
|
||||
obj.AddComponent<MetaCorePrefabInstanceMetadata>(data.PrefabInstance.value());
|
||||
if (data.ModelRootTag.has_value())
|
||||
|
||||
@ -119,6 +119,19 @@ static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCo
|
||||
}
|
||||
objJson["Rotator"] = rotatorJson;
|
||||
}
|
||||
} else if (objField.Name == "Script" && obj.Script.has_value()) {
|
||||
json scriptJson = json::object();
|
||||
json instancesJson = json::array();
|
||||
for (const MetaCoreScriptInstanceData& instance : obj.Script->Instances) {
|
||||
json instanceJson = json::object();
|
||||
instanceJson["InstanceId"] = instance.InstanceId;
|
||||
instanceJson["ScriptTypeId"] = instance.ScriptTypeId;
|
||||
instanceJson["Enabled"] = instance.Enabled;
|
||||
instanceJson["FieldsJson"] = instance.FieldsJson;
|
||||
instancesJson.push_back(std::move(instanceJson));
|
||||
}
|
||||
scriptJson["Instances"] = std::move(instancesJson);
|
||||
objJson["Script"] = std::move(scriptJson);
|
||||
} else if (objField.Name == "Camera" && obj.Camera.has_value()) {
|
||||
const MetaCoreStructDescriptor* camDesc = registry.FindStruct<MetaCoreCameraComponent>();
|
||||
if (camDesc) {
|
||||
@ -238,6 +251,32 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me
|
||||
}
|
||||
obj.Rotator = rotator;
|
||||
}
|
||||
} else if (objField.Name == "Script" && objJson.contains("Script")) {
|
||||
const auto& scriptJson = objJson["Script"];
|
||||
MetaCoreScriptComponent script;
|
||||
if (scriptJson.contains("Instances") && scriptJson["Instances"].is_array()) {
|
||||
for (const auto& instanceJson : scriptJson["Instances"]) {
|
||||
MetaCoreScriptInstanceData instance;
|
||||
if (instanceJson.contains("InstanceId")) {
|
||||
instance.InstanceId = instanceJson["InstanceId"].get<std::uint64_t>();
|
||||
}
|
||||
if (instanceJson.contains("ScriptTypeId")) {
|
||||
instance.ScriptTypeId = instanceJson["ScriptTypeId"].get<std::string>();
|
||||
}
|
||||
if (instanceJson.contains("Enabled")) {
|
||||
instance.Enabled = instanceJson["Enabled"].get<bool>();
|
||||
}
|
||||
if (instanceJson.contains("FieldsJson")) {
|
||||
instance.FieldsJson = instanceJson["FieldsJson"].get<std::string>();
|
||||
}
|
||||
if (!instance.ScriptTypeId.empty()) {
|
||||
script.Instances.push_back(std::move(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!script.Instances.empty()) {
|
||||
obj.Script = std::move(script);
|
||||
}
|
||||
} else if (objField.Name == "Camera" && objJson.contains("Camera")) {
|
||||
const auto& camJson = objJson["Camera"];
|
||||
const MetaCoreStructDescriptor* camDesc = registry.FindStruct<MetaCoreCameraComponent>();
|
||||
|
||||
@ -142,6 +142,28 @@ struct MetaCoreRotatorComponent {
|
||||
float DegreesPerSecond = 90.0F;
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreScriptInstanceData {
|
||||
MC_GENERATED_BODY()
|
||||
|
||||
MC_PROPERTY()
|
||||
std::uint64_t InstanceId = 0;
|
||||
MC_PROPERTY()
|
||||
std::string ScriptTypeId{};
|
||||
MC_PROPERTY()
|
||||
bool Enabled = true;
|
||||
MC_PROPERTY()
|
||||
std::string FieldsJson = "{}";
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreScriptComponent {
|
||||
MC_GENERATED_BODY()
|
||||
|
||||
MC_PROPERTY()
|
||||
std::vector<MetaCoreScriptInstanceData> Instances{};
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreModelRootTag {
|
||||
MC_GENERATED_BODY()
|
||||
|
||||
@ -131,6 +131,8 @@ struct MetaCoreGameObjectData {
|
||||
std::optional<MetaCoreModelRootTag> ModelRootTag;
|
||||
MC_PROPERTY()
|
||||
std::optional<MetaCoreRotatorComponent> Rotator;
|
||||
MC_PROPERTY()
|
||||
std::optional<MetaCoreScriptComponent> Script;
|
||||
};
|
||||
|
||||
} // namespace MetaCore
|
||||
|
||||
@ -32,8 +32,10 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include <arpa/inet.h>
|
||||
@ -184,6 +186,43 @@ public:
|
||||
double LastLateDeltaSeconds = 0.0;
|
||||
};
|
||||
|
||||
struct MetaCoreScriptLifecycleCounters {
|
||||
int StartCount = 0;
|
||||
int FixedUpdateCount = 0;
|
||||
int UpdateCount = 0;
|
||||
int LateUpdateCount = 0;
|
||||
int StopCount = 0;
|
||||
};
|
||||
|
||||
class MetaCoreCountingNativeScript final : public MetaCore::MetaCoreIScriptBehaviour {
|
||||
public:
|
||||
explicit MetaCoreCountingNativeScript(std::shared_ptr<MetaCoreScriptLifecycleCounters> counters)
|
||||
: Counters_(std::move(counters)) {}
|
||||
|
||||
void OnPlayStart(MetaCore::MetaCoreScriptExecutionContext&) override {
|
||||
++Counters_->StartCount;
|
||||
}
|
||||
|
||||
void FixedUpdate(MetaCore::MetaCoreScriptExecutionContext&, double) override {
|
||||
++Counters_->FixedUpdateCount;
|
||||
}
|
||||
|
||||
void Update(MetaCore::MetaCoreScriptExecutionContext&, double) override {
|
||||
++Counters_->UpdateCount;
|
||||
}
|
||||
|
||||
void LateUpdate(MetaCore::MetaCoreScriptExecutionContext&, double) override {
|
||||
++Counters_->LateUpdateCount;
|
||||
}
|
||||
|
||||
void OnPlayStop(MetaCore::MetaCoreScriptExecutionContext&) override {
|
||||
++Counters_->StopCount;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<MetaCoreScriptLifecycleCounters> Counters_{};
|
||||
};
|
||||
|
||||
void MetaCoreTestSceneEditApi() {
|
||||
MetaCore::MetaCoreScene scene = MetaCore::MetaCoreCreateDefaultScene();
|
||||
const std::size_t originalCount = scene.GetGameObjects().size();
|
||||
@ -303,6 +342,7 @@ void MetaCoreTestBuiltinModuleComposition() {
|
||||
editorViewsModule->Shutdown(moduleRegistry);
|
||||
coreServicesModule->Shutdown(moduleRegistry);
|
||||
moduleRegistry.ShutdownServices();
|
||||
_putenv_s("METACORE_PROJECT_PATH", "");
|
||||
}
|
||||
|
||||
void MetaCoreTestLegacyBinarySceneStartupLoadCompatibility() {
|
||||
@ -573,32 +613,38 @@ void MetaCoreTestComponentRegistryDescriptors() {
|
||||
const auto* cameraDescriptor = componentRegistry->FindDescriptor("Camera");
|
||||
const auto* lightDescriptor = componentRegistry->FindDescriptor("Light");
|
||||
const auto* rotatorDescriptor = componentRegistry->FindDescriptor("Rotator");
|
||||
const auto* scriptDescriptor = componentRegistry->FindDescriptor("Script");
|
||||
const auto* meshRendererDescriptor = componentRegistry->FindDescriptor("MeshRenderer");
|
||||
|
||||
std::cout << "[CHECK] transformDescriptor: " << transformDescriptor << std::endl;
|
||||
std::cout << "[CHECK] cameraDescriptor: " << cameraDescriptor << std::endl;
|
||||
std::cout << "[CHECK] lightDescriptor: " << lightDescriptor << std::endl;
|
||||
std::cout << "[CHECK] rotatorDescriptor: " << rotatorDescriptor << std::endl;
|
||||
std::cout << "[CHECK] scriptDescriptor: " << scriptDescriptor << std::endl;
|
||||
std::cout << "[CHECK] meshRendererDescriptor: " << meshRendererDescriptor << std::endl;
|
||||
if (transformDescriptor) std::cout << "[CHECK] transform DrawInspector: " << static_cast<bool>(transformDescriptor->DrawInspector) << std::endl;
|
||||
if (cameraDescriptor) std::cout << "[CHECK] camera DrawInspector: " << static_cast<bool>(cameraDescriptor->DrawInspector) << std::endl;
|
||||
if (lightDescriptor) std::cout << "[CHECK] light DrawInspector: " << static_cast<bool>(lightDescriptor->DrawInspector) << std::endl;
|
||||
if (rotatorDescriptor) std::cout << "[CHECK] rotator DrawInspector: " << static_cast<bool>(rotatorDescriptor->DrawInspector) << std::endl;
|
||||
if (scriptDescriptor) std::cout << "[CHECK] script DrawInspector: " << static_cast<bool>(scriptDescriptor->DrawInspector) << std::endl;
|
||||
if (meshRendererDescriptor) std::cout << "[CHECK] meshRenderer DrawInspector: " << static_cast<bool>(meshRendererDescriptor->DrawInspector) << std::endl;
|
||||
|
||||
MetaCoreExpect(transformDescriptor != nullptr, "Transform descriptor 应存在");
|
||||
MetaCoreExpect(cameraDescriptor != nullptr, "Camera descriptor 应存在");
|
||||
MetaCoreExpect(lightDescriptor != nullptr, "Light descriptor 应存在");
|
||||
MetaCoreExpect(rotatorDescriptor != nullptr, "Rotator descriptor 应存在");
|
||||
MetaCoreExpect(scriptDescriptor != nullptr, "Script descriptor 应存在");
|
||||
MetaCoreExpect(meshRendererDescriptor != nullptr, "MeshRenderer descriptor 应存在");
|
||||
MetaCoreExpect(!transformDescriptor->DrawInspector, "Transform 应继续由独立 InspectorDrawer 处理");
|
||||
MetaCoreExpect(static_cast<bool>(cameraDescriptor->DrawInspector), "Camera descriptor 应提供 drawer");
|
||||
MetaCoreExpect(static_cast<bool>(lightDescriptor->DrawInspector), "Light descriptor 应提供 drawer");
|
||||
MetaCoreExpect(static_cast<bool>(rotatorDescriptor->DrawInspector), "Rotator descriptor 应提供 drawer");
|
||||
MetaCoreExpect(static_cast<bool>(scriptDescriptor->DrawInspector), "Script descriptor 应提供 drawer");
|
||||
MetaCoreExpect(static_cast<bool>(meshRendererDescriptor->DrawInspector), "MeshRenderer descriptor 应提供 drawer");
|
||||
|
||||
coreServicesModule->Shutdown(moduleRegistry);
|
||||
moduleRegistry.ShutdownServices();
|
||||
_putenv_s("METACORE_PROJECT_PATH", "");
|
||||
}
|
||||
|
||||
void MetaCoreTestPlayModeStateMachineAndLifecycle() {
|
||||
@ -770,6 +816,320 @@ void MetaCoreTestPlayModeRotatorRetainsChangesAsSingleUndoStep() {
|
||||
moduleRegistry.ShutdownServices();
|
||||
}
|
||||
|
||||
void MetaCoreTestNativeScriptRegistryAndDefaults() {
|
||||
MetaCore::MetaCoreEditorModuleRegistry moduleRegistry;
|
||||
auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule();
|
||||
coreServicesModule->Startup(moduleRegistry);
|
||||
|
||||
const auto scriptRegistry = moduleRegistry.ResolveService<MetaCore::MetaCoreIScriptRegistry>();
|
||||
MetaCoreExpect(scriptRegistry != nullptr, "应解析到 ScriptRegistry");
|
||||
|
||||
const auto* rotatorDefinition = scriptRegistry->FindScript("MetaCore.Script.Rotator");
|
||||
const auto* moverDefinition = scriptRegistry->FindScript("MetaCore.Script.Mover");
|
||||
MetaCoreExpect(rotatorDefinition != nullptr, "应注册内置 RotatorScript");
|
||||
MetaCoreExpect(moverDefinition != nullptr, "应注册内置 MoverScript");
|
||||
MetaCoreExpect(scriptRegistry->CreateBehaviour("MetaCore.Script.Rotator") != nullptr, "RotatorScript 应能创建运行时对象");
|
||||
|
||||
const nlohmann::json rotatorDefaults = nlohmann::json::parse(scriptRegistry->BuildDefaultFieldsJson("MetaCore.Script.Rotator"));
|
||||
MetaCoreExpect(rotatorDefaults.contains("Enabled") && rotatorDefaults["Enabled"].get<bool>(), "RotatorScript 默认应启用");
|
||||
MetaCoreExpect(rotatorDefaults.contains("DegreesPerSecond"), "RotatorScript 默认字段应包含角速度");
|
||||
|
||||
const nlohmann::json moverEnsured =
|
||||
nlohmann::json::parse(scriptRegistry->EnsureFieldsJsonDefaults("MetaCore.Script.Mover", "{}"));
|
||||
MetaCoreExpect(moverEnsured.contains("Enabled"), "MoverScript 应补齐 Enabled 默认字段");
|
||||
MetaCoreExpect(moverEnsured.contains("Velocity"), "MoverScript 应补齐 Velocity 默认字段");
|
||||
|
||||
MetaCore::MetaCoreScriptDefinition replacement;
|
||||
replacement.TypeId = "MetaCore.Script.Mover";
|
||||
replacement.DisplayName = "替换移动脚本";
|
||||
replacement.Fields = {
|
||||
MetaCore::MetaCoreScriptFieldDefinition{
|
||||
"Label",
|
||||
"标签",
|
||||
MetaCore::MetaCoreScriptFieldType::String,
|
||||
false,
|
||||
0.0F,
|
||||
glm::vec3(0.0F),
|
||||
"demo"
|
||||
}
|
||||
};
|
||||
replacement.Factory = []() { return std::make_unique<MetaCoreCountingNativeScript>(std::make_shared<MetaCoreScriptLifecycleCounters>()); };
|
||||
scriptRegistry->RegisterScript(std::move(replacement));
|
||||
const auto* replacedDefinition = scriptRegistry->FindScript("MetaCore.Script.Mover");
|
||||
MetaCoreExpect(replacedDefinition != nullptr && replacedDefinition->DisplayName == "替换移动脚本", "重复 TypeId 注册应替换旧定义");
|
||||
MetaCoreExpect(scriptRegistry->CreateBehaviour("MetaCore.Script.Mover") != nullptr, "替换后的脚本应能创建运行时对象");
|
||||
|
||||
coreServicesModule->Shutdown(moduleRegistry);
|
||||
moduleRegistry.ShutdownServices();
|
||||
}
|
||||
|
||||
void MetaCoreTestScriptComponentSerializationSnapshotAndJson() {
|
||||
MetaCore::MetaCoreTypeRegistry registry;
|
||||
MetaCore::MetaCoreRegisterFoundationGeneratedTypes(registry);
|
||||
MetaCore::MetaCoreRegisterSceneGeneratedTypes(registry);
|
||||
|
||||
MetaCore::MetaCoreScriptComponent component;
|
||||
component.Instances.push_back(MetaCore::MetaCoreScriptInstanceData{
|
||||
1001,
|
||||
"MetaCore.Script.Rotator",
|
||||
true,
|
||||
"{\"Enabled\":true,\"DegreesPerSecond\":45.0}"
|
||||
});
|
||||
|
||||
const auto serializedComponent = MetaCore::MetaCoreSerializeToBytes(component, registry);
|
||||
MetaCoreExpect(serializedComponent.has_value(), "Script 组件应可二进制序列化");
|
||||
MetaCore::MetaCoreScriptComponent roundTripComponent;
|
||||
MetaCoreExpect(
|
||||
MetaCore::MetaCoreDeserializeFromBytes(*serializedComponent, roundTripComponent, registry),
|
||||
"Script 组件应可二进制反序列化"
|
||||
);
|
||||
MetaCoreExpect(roundTripComponent.Instances.size() == 1, "Script 组件 round-trip 后实例数量应保留");
|
||||
MetaCoreExpect(roundTripComponent.Instances.front().ScriptTypeId == "MetaCore.Script.Rotator", "Script TypeId 应保留");
|
||||
MetaCoreExpect(roundTripComponent.Instances.front().FieldsJson == component.Instances.front().FieldsJson, "Script FieldsJson 应保留");
|
||||
|
||||
MetaCore::MetaCoreScene scene;
|
||||
MetaCore::MetaCoreGameObject object = scene.CreateGameObject("ScriptObject");
|
||||
object.AddComponent<MetaCore::MetaCoreScriptComponent>(component);
|
||||
const MetaCore::MetaCoreId objectId = object.GetId();
|
||||
|
||||
const MetaCore::MetaCoreSceneSnapshot snapshot = scene.CaptureSnapshot();
|
||||
MetaCoreExpect(snapshot.GameObjects.size() == 1, "快照应包含脚本对象");
|
||||
MetaCoreExpect(snapshot.GameObjects.front().Script.has_value(), "快照应保存 Script 组件");
|
||||
|
||||
scene.RestoreSnapshot(MetaCore::MetaCoreSceneSnapshot{});
|
||||
MetaCoreExpect(!scene.FindGameObject(objectId), "恢复空快照后对象应不存在");
|
||||
scene.RestoreSnapshot(snapshot);
|
||||
MetaCore::MetaCoreGameObject restoredObject = scene.FindGameObject(objectId);
|
||||
MetaCoreExpect(restoredObject && restoredObject.HasComponent<MetaCore::MetaCoreScriptComponent>(), "恢复快照后 Script 组件应保留");
|
||||
MetaCoreExpect(
|
||||
restoredObject.GetComponent<MetaCore::MetaCoreScriptComponent>().Instances.front().InstanceId == 1001,
|
||||
"恢复快照后脚本 InstanceId 应保留"
|
||||
);
|
||||
|
||||
const std::filesystem::path tempDirectory =
|
||||
std::filesystem::temp_directory_path() / "MetaCoreScriptSerialization";
|
||||
std::filesystem::remove_all(tempDirectory);
|
||||
std::filesystem::create_directories(tempDirectory);
|
||||
|
||||
MetaCore::MetaCoreSceneDocument sceneDocument;
|
||||
sceneDocument.Name = "ScriptScene";
|
||||
sceneDocument.GameObjects = scene.CaptureSnapshot().GameObjects;
|
||||
const std::filesystem::path scenePath = tempDirectory / "ScriptScene.mcscene.json";
|
||||
MetaCoreExpect(
|
||||
MetaCore::MetaCoreSceneSerializer::SaveSceneToJson(scenePath, sceneDocument, registry),
|
||||
"包含 Script 的场景 JSON 应可保存"
|
||||
);
|
||||
const auto loadedSceneDocument = MetaCore::MetaCoreSceneSerializer::LoadSceneFromJson(scenePath, registry);
|
||||
MetaCoreExpect(loadedSceneDocument.has_value(), "包含 Script 的场景 JSON 应可读取");
|
||||
MetaCoreExpect(!loadedSceneDocument->GameObjects.empty(), "读取后场景对象应保留");
|
||||
MetaCoreExpect(loadedSceneDocument->GameObjects.front().Script.has_value(), "读取后 Script 组件应保留");
|
||||
MetaCoreExpect(
|
||||
loadedSceneDocument->GameObjects.front().Script->Instances.front().FieldsJson == component.Instances.front().FieldsJson,
|
||||
"读取后 Script FieldsJson 应保留"
|
||||
);
|
||||
|
||||
const std::filesystem::path oldScenePath = tempDirectory / "OldSceneNoScript.mcscene.json";
|
||||
{
|
||||
std::ofstream oldSceneFile(oldScenePath, std::ios::trunc);
|
||||
oldSceneFile << "{\n"
|
||||
<< " \"SceneName\": \"OldScene\",\n"
|
||||
<< " \"GameObjects\": [\n"
|
||||
<< " {\"Id\": 42, \"ParentId\": 0, \"Name\": \"OldObject\", \"Transform\": {"
|
||||
<< "\"Position\": [0, 0, 0], \"RotationEulerDegrees\": [0, 0, 0], \"Scale\": [1, 1, 1]}}\n"
|
||||
<< " ]\n"
|
||||
<< "}\n";
|
||||
}
|
||||
const auto oldSceneDocument = MetaCore::MetaCoreSceneSerializer::LoadSceneFromJson(oldScenePath, registry);
|
||||
MetaCoreExpect(oldSceneDocument.has_value(), "旧场景缺少 Script 字段时仍应可读取");
|
||||
MetaCoreExpect(!oldSceneDocument->GameObjects.front().Script.has_value(), "旧场景缺少 Script 字段时不应生成脚本组件");
|
||||
|
||||
std::filesystem::remove_all(tempDirectory);
|
||||
}
|
||||
|
||||
void MetaCoreTestNativeScriptLifecycle() {
|
||||
_putenv_s("METACORE_PROJECT_PATH", "");
|
||||
|
||||
MetaCore::MetaCoreWindow window;
|
||||
MetaCore::MetaCoreRenderDevice renderDevice;
|
||||
MetaCore::MetaCoreEditorViewportRenderer viewportRenderer;
|
||||
MetaCore::MetaCoreScene scene;
|
||||
MetaCore::MetaCoreGameObject object = scene.CreateGameObject("ScriptLifecycleObject");
|
||||
auto& scriptComponent = object.AddComponent<MetaCore::MetaCoreScriptComponent>();
|
||||
scriptComponent.Instances.push_back(MetaCore::MetaCoreScriptInstanceData{
|
||||
2001,
|
||||
"MetaCore.Tests.CountingScript",
|
||||
true,
|
||||
"{}"
|
||||
});
|
||||
|
||||
MetaCore::MetaCoreLogService logService;
|
||||
MetaCore::MetaCoreEditorModuleRegistry moduleRegistry;
|
||||
auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule();
|
||||
coreServicesModule->Startup(moduleRegistry);
|
||||
|
||||
const auto counters = std::make_shared<MetaCoreScriptLifecycleCounters>();
|
||||
const auto scriptRegistry = moduleRegistry.ResolveService<MetaCore::MetaCoreIScriptRegistry>();
|
||||
MetaCoreExpect(scriptRegistry != nullptr, "应解析到 ScriptRegistry");
|
||||
MetaCore::MetaCoreScriptDefinition countingDefinition;
|
||||
countingDefinition.TypeId = "MetaCore.Tests.CountingScript";
|
||||
countingDefinition.DisplayName = "计数脚本";
|
||||
countingDefinition.Factory = [counters]() { return std::make_unique<MetaCoreCountingNativeScript>(counters); };
|
||||
scriptRegistry->RegisterScript(std::move(countingDefinition));
|
||||
|
||||
MetaCore::MetaCoreEditorContext editorContext(
|
||||
window,
|
||||
renderDevice,
|
||||
viewportRenderer,
|
||||
scene,
|
||||
logService,
|
||||
moduleRegistry
|
||||
);
|
||||
|
||||
const auto playMode = moduleRegistry.ResolveService<MetaCore::MetaCoreIPlayModeService>();
|
||||
MetaCoreExpect(playMode != nullptr, "应解析到 PlayModeService");
|
||||
MetaCoreExpect(playMode->EnterPlayMode(editorContext), "脚本生命周期测试应能进入播放模式");
|
||||
MetaCoreExpect(counters->StartCount == 1, "脚本 OnPlayStart 应只调用一次");
|
||||
|
||||
playMode->Tick(editorContext, 1.0 / 60.0);
|
||||
MetaCoreExpect(counters->FixedUpdateCount == 1, "脚本播放 Tick 应执行一次 FixedUpdate");
|
||||
MetaCoreExpect(counters->UpdateCount == 1, "脚本播放 Tick 应执行一次 Update");
|
||||
MetaCoreExpect(counters->LateUpdateCount == 1, "脚本播放 Tick 应执行一次 LateUpdate");
|
||||
|
||||
MetaCoreExpect(playMode->Pause(), "脚本生命周期测试应能暂停");
|
||||
playMode->Tick(editorContext, 1.0 / 60.0);
|
||||
MetaCoreExpect(counters->FixedUpdateCount == 1, "暂停时脚本 FixedUpdate 不应继续调用");
|
||||
MetaCoreExpect(counters->UpdateCount == 1, "暂停时脚本 Update 不应继续调用");
|
||||
MetaCoreExpect(counters->LateUpdateCount == 1, "暂停时脚本 LateUpdate 不应继续调用");
|
||||
|
||||
MetaCoreExpect(playMode->Step(editorContext), "暂停时脚本应能单步");
|
||||
MetaCoreExpect(counters->FixedUpdateCount == 2, "脚本单步应执行一次 FixedUpdate");
|
||||
MetaCoreExpect(counters->UpdateCount == 2, "脚本单步应执行一次 Update");
|
||||
MetaCoreExpect(counters->LateUpdateCount == 2, "脚本单步应执行一次 LateUpdate");
|
||||
|
||||
MetaCoreExpect(playMode->ExitPlayMode(editorContext), "脚本生命周期测试应能退出播放模式");
|
||||
MetaCoreExpect(counters->StopCount == 1, "脚本 OnPlayStop 应调用一次并清理运行时实例");
|
||||
|
||||
coreServicesModule->Shutdown(moduleRegistry);
|
||||
moduleRegistry.ShutdownServices();
|
||||
}
|
||||
|
||||
void MetaCoreTestNativeScriptBehavioursRetainChangesAsSingleUndoStep() {
|
||||
_putenv_s("METACORE_PROJECT_PATH", "");
|
||||
|
||||
MetaCore::MetaCoreWindow window;
|
||||
MetaCore::MetaCoreRenderDevice renderDevice;
|
||||
MetaCore::MetaCoreEditorViewportRenderer viewportRenderer;
|
||||
MetaCore::MetaCoreScene scene;
|
||||
MetaCore::MetaCoreGameObject object = scene.CreateGameObject("ScriptActor");
|
||||
auto& scriptComponent = object.AddComponent<MetaCore::MetaCoreScriptComponent>();
|
||||
scriptComponent.Instances.push_back(MetaCore::MetaCoreScriptInstanceData{
|
||||
3001,
|
||||
"MetaCore.Script.Rotator",
|
||||
true,
|
||||
"{\"Enabled\":true,\"DegreesPerSecond\":60.0}"
|
||||
});
|
||||
scriptComponent.Instances.push_back(MetaCore::MetaCoreScriptInstanceData{
|
||||
3002,
|
||||
"MetaCore.Script.Mover",
|
||||
true,
|
||||
"{\"Enabled\":true,\"Velocity\":[2.0,0.0,0.0]}"
|
||||
});
|
||||
const MetaCore::MetaCoreId objectId = object.GetId();
|
||||
|
||||
MetaCore::MetaCoreLogService logService;
|
||||
MetaCore::MetaCoreEditorModuleRegistry moduleRegistry;
|
||||
auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule();
|
||||
coreServicesModule->Startup(moduleRegistry);
|
||||
|
||||
MetaCore::MetaCoreEditorContext editorContext(
|
||||
window,
|
||||
renderDevice,
|
||||
viewportRenderer,
|
||||
scene,
|
||||
logService,
|
||||
moduleRegistry
|
||||
);
|
||||
|
||||
const auto playMode = moduleRegistry.ResolveService<MetaCore::MetaCoreIPlayModeService>();
|
||||
const auto scenePersistence = moduleRegistry.ResolveService<MetaCore::MetaCoreIScenePersistenceService>();
|
||||
MetaCoreExpect(playMode != nullptr, "应解析到 PlayModeService");
|
||||
MetaCoreExpect(scenePersistence != nullptr, "应解析到 ScenePersistenceService");
|
||||
MetaCoreExpect(editorContext.GetCommandService().GetUndoCount() == 0, "脚本播放前 Undo 栈应为空");
|
||||
|
||||
MetaCoreExpect(playMode->EnterPlayMode(editorContext), "脚本行为测试应能进入播放模式");
|
||||
for (int frameIndex = 0; frameIndex < 5; ++frameIndex) {
|
||||
playMode->Tick(editorContext, 0.1);
|
||||
}
|
||||
|
||||
MetaCore::MetaCoreGameObject updatedObject = scene.FindGameObject(objectId);
|
||||
MetaCoreExpectVec3Near(
|
||||
updatedObject.GetComponent<MetaCore::MetaCoreTransformComponent>().RotationEulerDegrees,
|
||||
glm::vec3(0.0F, 30.0F, 0.0F),
|
||||
"RotatorScript 播放 0.5 秒后应旋转 30 度"
|
||||
);
|
||||
MetaCoreExpectVec3Near(
|
||||
updatedObject.GetComponent<MetaCore::MetaCoreTransformComponent>().Position,
|
||||
glm::vec3(1.0F, 0.0F, 0.0F),
|
||||
"MoverScript 播放 0.5 秒后应移动 1 个单位"
|
||||
);
|
||||
|
||||
MetaCoreExpect(playMode->Pause(), "脚本行为测试应能暂停");
|
||||
playMode->Tick(editorContext, 0.5);
|
||||
updatedObject = scene.FindGameObject(objectId);
|
||||
MetaCoreExpectVec3Near(
|
||||
updatedObject.GetComponent<MetaCore::MetaCoreTransformComponent>().RotationEulerDegrees,
|
||||
glm::vec3(0.0F, 30.0F, 0.0F),
|
||||
"暂停期间 RotatorScript 不应继续旋转"
|
||||
);
|
||||
MetaCoreExpectVec3Near(
|
||||
updatedObject.GetComponent<MetaCore::MetaCoreTransformComponent>().Position,
|
||||
glm::vec3(1.0F, 0.0F, 0.0F),
|
||||
"暂停期间 MoverScript 不应继续移动"
|
||||
);
|
||||
|
||||
MetaCoreExpect(playMode->Step(editorContext), "暂停后脚本行为应能单步");
|
||||
updatedObject = scene.FindGameObject(objectId);
|
||||
MetaCoreExpectVec3Near(
|
||||
updatedObject.GetComponent<MetaCore::MetaCoreTransformComponent>().RotationEulerDegrees,
|
||||
glm::vec3(0.0F, 31.0F, 0.0F),
|
||||
"RotatorScript 单步应按固定步长旋转"
|
||||
);
|
||||
MetaCoreExpectVec3Near(
|
||||
updatedObject.GetComponent<MetaCore::MetaCoreTransformComponent>().Position,
|
||||
glm::vec3(1.0F + (2.0F / 60.0F), 0.0F, 0.0F),
|
||||
"MoverScript 单步应按固定步长移动"
|
||||
);
|
||||
|
||||
MetaCoreExpect(playMode->ExitPlayMode(editorContext), "脚本行为测试应能退出播放模式");
|
||||
MetaCoreExpect(editorContext.GetCommandService().GetUndoCount() == 1, "脚本播放期差异应合并为一个 Undo 步");
|
||||
MetaCoreExpect(scenePersistence->IsSceneDirty(), "脚本播放结果保留后场景应标记为脏");
|
||||
updatedObject = scene.FindGameObject(objectId);
|
||||
MetaCoreExpect(updatedObject.HasComponent<MetaCore::MetaCoreScriptComponent>(), "停止后 Script 组件应保留");
|
||||
MetaCoreExpectVec3Near(
|
||||
updatedObject.GetComponent<MetaCore::MetaCoreTransformComponent>().RotationEulerDegrees,
|
||||
glm::vec3(0.0F, 31.0F, 0.0F),
|
||||
"停止后 RotatorScript 结果应保留"
|
||||
);
|
||||
|
||||
MetaCoreExpect(editorContext.UndoCommand(), "Undo 应能回到脚本播放前快照");
|
||||
updatedObject = scene.FindGameObject(objectId);
|
||||
MetaCoreExpectVec3Near(
|
||||
updatedObject.GetComponent<MetaCore::MetaCoreTransformComponent>().RotationEulerDegrees,
|
||||
glm::vec3(0.0F, 0.0F, 0.0F),
|
||||
"Undo 后 RotatorScript 旋转应回到播放前"
|
||||
);
|
||||
MetaCoreExpectVec3Near(
|
||||
updatedObject.GetComponent<MetaCore::MetaCoreTransformComponent>().Position,
|
||||
glm::vec3(0.0F, 0.0F, 0.0F),
|
||||
"Undo 后 MoverScript 位移应回到播放前"
|
||||
);
|
||||
MetaCoreExpect(updatedObject.HasComponent<MetaCore::MetaCoreScriptComponent>(), "Undo 后 Script 组件应保留");
|
||||
MetaCoreExpect(updatedObject.GetComponent<MetaCore::MetaCoreScriptComponent>().Instances.size() == 2, "Undo 后脚本实例列表应保留");
|
||||
|
||||
coreServicesModule->Shutdown(moduleRegistry);
|
||||
moduleRegistry.ShutdownServices();
|
||||
}
|
||||
|
||||
void MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot() {
|
||||
MetaCore::MetaCoreScene scene;
|
||||
|
||||
@ -1855,6 +2215,13 @@ void MetaCoreTestPrefabWorkflow() {
|
||||
|
||||
MetaCore::MetaCoreGameObject root = scene.CreateGameObject("PrefabRoot");
|
||||
root.AddComponent<MetaCore::MetaCoreMeshRendererComponent>();
|
||||
auto& rootScript = root.AddComponent<MetaCore::MetaCoreScriptComponent>();
|
||||
rootScript.Instances.push_back(MetaCore::MetaCoreScriptInstanceData{
|
||||
4101,
|
||||
"MetaCore.Script.Mover",
|
||||
true,
|
||||
"{\"Enabled\":true,\"Velocity\":[1.0,2.0,3.0]}"
|
||||
});
|
||||
root.GetComponent<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(1.0F, 2.0F, 3.0F);
|
||||
const MetaCore::MetaCoreId rootId = root.GetId();
|
||||
MetaCore::MetaCoreGameObject child = scene.CreateGameObject("PrefabChild", rootId);
|
||||
@ -1887,6 +2254,11 @@ void MetaCoreTestPrefabWorkflow() {
|
||||
MetaCore::MetaCoreGameObject instantiatedRoot = scene.FindGameObject(*instantiatedRootId);
|
||||
MetaCoreExpect(instantiatedRoot, "实例化后应能找到根对象");
|
||||
MetaCoreExpect(instantiatedRoot.HasComponent<MetaCore::MetaCorePrefabInstanceMetadata>(), "实例根应带有 prefab 元数据");
|
||||
MetaCoreExpect(instantiatedRoot.HasComponent<MetaCore::MetaCoreScriptComponent>(), "实例根应保留 Script 组件");
|
||||
MetaCoreExpect(
|
||||
instantiatedRoot.GetComponent<MetaCore::MetaCoreScriptComponent>().Instances.size() == 1,
|
||||
"Prefab 实例应保留脚本实例"
|
||||
);
|
||||
|
||||
instantiatedRoot.GetComponent<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(8.0F, 9.0F, 10.0F);
|
||||
editorContext.SelectOnly(instantiatedRoot.GetId());
|
||||
@ -1895,6 +2267,11 @@ void MetaCoreTestPrefabWorkflow() {
|
||||
const auto appliedPrefabDocument = MetaCore::MetaCoreSceneSerializer::LoadPrefabFromJson(tempProjectRoot / prefabPath, reflectionRegistry->GetTypeRegistry());
|
||||
MetaCoreExpect(appliedPrefabDocument.has_value(), "Apply 后 prefab 应能被 JSON 解析");
|
||||
MetaCoreExpect(!appliedPrefabDocument->GameObjects.empty(), "Apply 后 prefab 应保留对象数据");
|
||||
MetaCoreExpect(appliedPrefabDocument->GameObjects.front().Script.has_value(), "Apply 后 prefab 资源应保留 Script 组件");
|
||||
MetaCoreExpect(
|
||||
appliedPrefabDocument->GameObjects.front().Script->Instances.front().ScriptTypeId == "MetaCore.Script.Mover",
|
||||
"Apply 后 prefab 资源应保留脚本 TypeId"
|
||||
);
|
||||
MetaCoreExpectVec3Near(
|
||||
appliedPrefabDocument->GameObjects.front().Transform.Position,
|
||||
glm::vec3(8.0F, 9.0F, 10.0F),
|
||||
@ -1905,6 +2282,7 @@ void MetaCoreTestPrefabWorkflow() {
|
||||
MetaCoreExpect(secondInstanceRootId.has_value(), "应用后应仍能再次实例化 prefab");
|
||||
MetaCore::MetaCoreGameObject secondInstanceRoot = scene.FindGameObject(*secondInstanceRootId);
|
||||
MetaCoreExpect(secondInstanceRoot, "第二个实例应存在");
|
||||
MetaCoreExpect(secondInstanceRoot.HasComponent<MetaCore::MetaCoreScriptComponent>(), "第二个实例应保留 Script 组件");
|
||||
MetaCoreExpectVec3Near(secondInstanceRoot.GetComponent<MetaCore::MetaCoreTransformComponent>().Position, glm::vec3(8.0F, 9.0F, 10.0F), "Apply 后新实例应继承修改过的 transform");
|
||||
|
||||
secondInstanceRoot.GetComponent<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(-1.0F, -2.0F, -3.0F);
|
||||
@ -2067,8 +2445,10 @@ void MetaCoreTestComponentRegistryOperations() {
|
||||
|
||||
const auto* cameraDescriptor = componentRegistry->FindDescriptor("Camera");
|
||||
const auto* lightDescriptor = componentRegistry->FindDescriptor("Light");
|
||||
const auto* scriptDescriptor = componentRegistry->FindDescriptor("Script");
|
||||
MetaCoreExpect(cameraDescriptor != nullptr, "应能找到 Camera 描述符");
|
||||
MetaCoreExpect(lightDescriptor != nullptr, "应能找到 Light 描述符");
|
||||
MetaCoreExpect(scriptDescriptor != nullptr, "应能找到 Script 描述符");
|
||||
MetaCoreExpect(!cameraDescriptor->HasComponent(object), "初始时不应有 Camera");
|
||||
|
||||
const bool addedCamera = editorContext.ExecuteSnapshotCommand("添加 Camera", [&]() {
|
||||
@ -2083,6 +2463,18 @@ void MetaCoreTestComponentRegistryOperations() {
|
||||
MetaCoreExpect(addedLight, "应能通过注册表添加 Light");
|
||||
MetaCoreExpect(object.HasComponent<MetaCore::MetaCoreLightComponent>(), "添加后对象应拥有 Light");
|
||||
|
||||
const bool addedScript = editorContext.ExecuteSnapshotCommand("添加 Script", [&]() {
|
||||
return scriptDescriptor->AddComponent(object);
|
||||
});
|
||||
MetaCoreExpect(addedScript, "应能通过注册表添加 Script");
|
||||
MetaCoreExpect(object.HasComponent<MetaCore::MetaCoreScriptComponent>(), "添加后对象应拥有 Script");
|
||||
object.GetComponent<MetaCore::MetaCoreScriptComponent>().Instances.push_back(MetaCore::MetaCoreScriptInstanceData{
|
||||
5101,
|
||||
"MetaCore.Script.Rotator",
|
||||
true,
|
||||
"{\"Enabled\":true,\"DegreesPerSecond\":120.0}"
|
||||
});
|
||||
|
||||
const bool removedCamera = editorContext.ExecuteSnapshotCommand("移除 Camera", [&]() {
|
||||
return cameraDescriptor->RemoveComponent(object);
|
||||
});
|
||||
@ -2114,10 +2506,26 @@ void MetaCoreTestComponentRegistryOperations() {
|
||||
MetaCoreExpect(pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().IsPrimary, "粘贴后 IsPrimary 应一致");
|
||||
MetaCoreExpect(std::abs(secondPasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().FieldOfViewDegrees - 77.0F) <= 0.0001F, "第二个对象粘贴后 FOV 应一致");
|
||||
|
||||
MetaCoreExpect(componentRegistry->CopyComponent("Script", object), "应能复制 Script 组件值");
|
||||
MetaCoreExpect(componentRegistry->CanPasteComponent("Script"), "复制后应可粘贴 Script");
|
||||
MetaCoreExpect(componentRegistry->PasteComponent("Script", pasteTarget), "应能粘贴 Script 组件值");
|
||||
MetaCoreExpect(pasteTarget.HasComponent<MetaCore::MetaCoreScriptComponent>(), "粘贴后目标对象应拥有 Script");
|
||||
MetaCoreExpect(
|
||||
pasteTarget.GetComponent<MetaCore::MetaCoreScriptComponent>().Instances.size() == 1,
|
||||
"粘贴后 Script 实例数量应一致"
|
||||
);
|
||||
MetaCoreExpect(
|
||||
pasteTarget.GetComponent<MetaCore::MetaCoreScriptComponent>().Instances.front().ScriptTypeId == "MetaCore.Script.Rotator",
|
||||
"粘贴后 Script TypeId 应一致"
|
||||
);
|
||||
|
||||
MetaCoreExpect(cameraDescriptor->ResetComponent != nullptr, "Camera descriptor 应提供 Reset");
|
||||
pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().FieldOfViewDegrees = 10.0F;
|
||||
MetaCoreExpect(cameraDescriptor->ResetComponent(pasteTarget), "应能重置 Camera");
|
||||
MetaCoreExpect(std::abs(pasteTarget.GetComponent<MetaCore::MetaCoreCameraComponent>().FieldOfViewDegrees - 60.0F) <= 0.0001F, "重置后 Camera 应恢复默认值");
|
||||
MetaCoreExpect(scriptDescriptor->ResetComponent != nullptr, "Script descriptor 应提供 Reset");
|
||||
MetaCoreExpect(scriptDescriptor->ResetComponent(pasteTarget), "应能重置 Script");
|
||||
MetaCoreExpect(pasteTarget.GetComponent<MetaCore::MetaCoreScriptComponent>().Instances.empty(), "重置后 Script 实例列表应清空");
|
||||
|
||||
coreServicesModule->Shutdown(moduleRegistry);
|
||||
moduleRegistry.ShutdownServices();
|
||||
@ -3597,6 +4005,14 @@ int main() {
|
||||
MetaCoreTestPlayModeStateMachineAndLifecycle();
|
||||
std::cout << "[RUN] MetaCoreTestPlayModeRotatorRetainsChangesAsSingleUndoStep..." << std::endl;
|
||||
MetaCoreTestPlayModeRotatorRetainsChangesAsSingleUndoStep();
|
||||
std::cout << "[RUN] MetaCoreTestNativeScriptRegistryAndDefaults..." << std::endl;
|
||||
MetaCoreTestNativeScriptRegistryAndDefaults();
|
||||
std::cout << "[RUN] MetaCoreTestScriptComponentSerializationSnapshotAndJson..." << std::endl;
|
||||
MetaCoreTestScriptComponentSerializationSnapshotAndJson();
|
||||
std::cout << "[RUN] MetaCoreTestNativeScriptLifecycle..." << std::endl;
|
||||
MetaCoreTestNativeScriptLifecycle();
|
||||
std::cout << "[RUN] MetaCoreTestNativeScriptBehavioursRetainChangesAsSingleUndoStep..." << std::endl;
|
||||
MetaCoreTestNativeScriptBehavioursRetainChangesAsSingleUndoStep();
|
||||
std::cout << "[RUN] MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot..." << std::endl;
|
||||
MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot();
|
||||
std::cout << "[RUN] MetaCoreTestJsonSceneSaveCurrentSceneUsesMcsceneJson..." << std::endl;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user