Advance editor runtime and scripting integration

This commit is contained in:
2026-04-29 16:55:44 +08:00
parent 631bf32db2
commit 2e50c90167
60 changed files with 4071 additions and 228 deletions

View File

@@ -19,6 +19,8 @@
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Scene/Scene.h>
#include <XCEngine/Scene/SceneManager.h>
#include <XCEngine/Scripting/ScriptComponent.h>
#include <XCEngine/Scripting/ScriptEngine.h>
#include <algorithm>
#include <charconv>
@@ -44,10 +46,15 @@ using ::XCEngine::Components::Scene;
using ::XCEngine::Components::SceneManager;
using ::XCEngine::Components::TransformComponent;
using ::XCEngine::Math::Quaternion;
using ::XCEngine::Math::Vector2;
using ::XCEngine::Math::Vector3;
using ::XCEngine::Math::Vector4;
using ::XCEngine::Resources::ResourceManager;
using ::XCEngine::Scripting::ScriptComponent;
constexpr char kComponentIdSeparator = '#';
constexpr std::string_view kScriptClassPropertyPath = "script_class";
constexpr std::string_view kScriptFieldPropertyPrefix = "script_field.";
void TraceSceneStartup(std::string message) {
::XCEngine::UI::Editor::AppendUIEditorRuntimeTrace("startup", std::move(message));
@@ -793,6 +800,104 @@ public:
void SetReceiveShadows(bool value) { MutableComponent().SetReceiveShadows(value); }
};
class ScriptComponentViewAdapter final
: public ComponentViewAdapterBase<EditorSceneScriptComponentView, ScriptComponent> {
public:
explicit ScriptComponentViewAdapter(ScriptComponent& component)
: ComponentViewAdapterBase(component, "ScriptComponent") {}
std::uint64_t GetScriptComponentUUID() const override {
return ComponentRef().GetScriptComponentUUID();
}
bool HasScriptClass() const override {
return ComponentRef().HasScriptClass();
}
std::string GetAssemblyName() const override {
return ComponentRef().GetAssemblyName();
}
std::string GetNamespaceName() const override {
return ComponentRef().GetNamespaceName();
}
std::string GetClassName() const override {
return ComponentRef().GetClassName();
}
std::string GetFullClassName() const override {
return ComponentRef().GetFullClassName();
}
bool TryGetFieldModel(
::XCEngine::Scripting::ScriptFieldModel& outModel) const override {
return ::XCEngine::Scripting::ScriptEngine::Get().TryGetScriptFieldModel(
&ComponentRef(),
outModel);
}
};
std::string EncodeScriptClassSelection(
std::string_view assemblyName,
std::string_view namespaceName,
std::string_view className) {
if (className.empty()) {
return {};
}
std::string encoded = std::string(assemblyName);
encoded.push_back('|');
encoded += namespaceName;
encoded.push_back('|');
encoded += className;
return encoded;
}
bool DecodeScriptClassSelection(
std::string_view encodedValue,
std::string& outAssemblyName,
std::string& outNamespaceName,
std::string& outClassName) {
outAssemblyName.clear();
outNamespaceName.clear();
outClassName.clear();
if (encodedValue.empty()) {
return true;
}
const std::size_t firstSeparator = encodedValue.find('|');
if (firstSeparator == std::string_view::npos) {
return false;
}
const std::size_t secondSeparator =
encodedValue.find('|', firstSeparator + 1u);
if (secondSeparator == std::string_view::npos) {
return false;
}
outAssemblyName = std::string(encodedValue.substr(0u, firstSeparator));
outNamespaceName = std::string(encodedValue.substr(
firstSeparator + 1u,
secondSeparator - firstSeparator - 1u));
outClassName = std::string(encodedValue.substr(secondSeparator + 1u));
return !outClassName.empty();
}
bool TryParseScriptFieldProperty(
std::string_view propertyPath,
std::string& outFieldName) {
if (propertyPath.size() <= kScriptFieldPropertyPrefix.size() ||
propertyPath.substr(0u, kScriptFieldPropertyPrefix.size()) !=
kScriptFieldPropertyPrefix) {
return false;
}
outFieldName = std::string(propertyPath.substr(kScriptFieldPropertyPrefix.size()));
return !outFieldName.empty();
}
std::shared_ptr<EditorSceneComponentView> CreateComponentView(Component& component) {
if (auto* transform = dynamic_cast<TransformComponent*>(&component); transform != nullptr) {
return std::make_shared<TransformComponentViewAdapter>(*transform);
@@ -847,6 +952,10 @@ std::shared_ptr<EditorSceneComponentView> CreateComponentView(Component& compone
volumeRenderer != nullptr) {
return std::make_shared<VolumeRendererComponentViewAdapter>(*volumeRenderer);
}
if (auto* scriptComponent = dynamic_cast<ScriptComponent*>(&component);
scriptComponent != nullptr) {
return std::make_shared<ScriptComponentViewAdapter>(*scriptComponent);
}
return std::make_shared<GenericComponentViewAdapter>(component.GetName());
}
@@ -1456,6 +1565,186 @@ bool ApplyVolumeRendererComponentMutation(
[&view](bool value) { view.SetReceiveShadows(value); });
}
bool TryResolveScriptFieldType(
const ::XCEngine::Scripting::ScriptFieldModel& model,
std::string_view fieldName,
::XCEngine::Scripting::ScriptFieldType& outFieldType) {
for (const ::XCEngine::Scripting::ScriptFieldSnapshot& field : model.fields) {
if (field.metadata.name == fieldName) {
outFieldType = field.metadata.type;
return true;
}
}
return false;
}
bool TryResolveScriptFieldValue(
const EditorSceneComponentMutation& mutation,
::XCEngine::Scripting::ScriptFieldType fieldType,
::XCEngine::Scripting::ScriptFieldValue& outValue) {
using ::XCEngine::Scripting::ScriptFieldType;
using ::XCEngine::Scripting::ScriptFieldValue;
switch (fieldType) {
case ScriptFieldType::Float: {
const float* value = TryGetMutationValue<float>(mutation);
if (value == nullptr) {
return false;
}
outValue = *value;
return true;
}
case ScriptFieldType::Double: {
const double* value = TryGetMutationValue<double>(mutation);
if (value == nullptr) {
return false;
}
outValue = *value;
return true;
}
case ScriptFieldType::Bool: {
const bool* value = TryGetMutationValue<bool>(mutation);
if (value == nullptr) {
return false;
}
outValue = *value;
return true;
}
case ScriptFieldType::Int32: {
const std::int32_t* value = TryGetMutationValue<std::int32_t>(mutation);
if (value == nullptr) {
return false;
}
outValue = *value;
return true;
}
case ScriptFieldType::UInt64: {
const std::uint64_t* value = TryGetMutationValue<std::uint64_t>(mutation);
if (value == nullptr) {
return false;
}
outValue = *value;
return true;
}
case ScriptFieldType::String: {
const std::string* value = TryGetMutationValue<std::string>(mutation);
if (value == nullptr) {
return false;
}
outValue = *value;
return true;
}
case ScriptFieldType::Vector2: {
const Vector2* value = TryGetMutationValue<Vector2>(mutation);
if (value == nullptr) {
return false;
}
outValue = *value;
return true;
}
case ScriptFieldType::Vector3: {
const Vector3* value = TryGetMutationValue<Vector3>(mutation);
if (value == nullptr) {
return false;
}
outValue = *value;
return true;
}
case ScriptFieldType::Vector4: {
const Vector4* value = TryGetMutationValue<Vector4>(mutation);
if (value == nullptr) {
return false;
}
outValue = *value;
return true;
}
case ScriptFieldType::GameObject: {
const ::XCEngine::Scripting::GameObjectReference* value =
TryGetMutationValue<::XCEngine::Scripting::GameObjectReference>(mutation);
if (value == nullptr) {
return false;
}
outValue = *value;
return true;
}
case ScriptFieldType::Component: {
const ::XCEngine::Scripting::ComponentReference* value =
TryGetMutationValue<::XCEngine::Scripting::ComponentReference>(mutation);
if (value == nullptr) {
return false;
}
outValue = *value;
return true;
}
case ScriptFieldType::None:
default:
return false;
}
}
bool ApplyScriptComponentMutation(
ScriptComponent& component,
const EditorSceneComponentMutation& mutation) {
if (mutation.propertyPath == kScriptClassPropertyPath) {
const std::string* encodedValue = TryGetMutationValue<std::string>(mutation);
if (encodedValue == nullptr) {
return false;
}
if (encodedValue->empty()) {
component.ClearScriptClass();
return true;
}
std::string assemblyName = {};
std::string namespaceName = {};
std::string className = {};
if (!DecodeScriptClassSelection(
*encodedValue,
assemblyName,
namespaceName,
className)) {
return false;
}
component.SetScriptClass(assemblyName, namespaceName, className);
return true;
}
std::string fieldName = {};
if (!TryParseScriptFieldProperty(mutation.propertyPath, fieldName)) {
return false;
}
::XCEngine::Scripting::ScriptFieldModel model = {};
if (!::XCEngine::Scripting::ScriptEngine::Get().TryGetScriptFieldModel(
&component,
model)) {
return false;
}
::XCEngine::Scripting::ScriptFieldType fieldType =
::XCEngine::Scripting::ScriptFieldType::None;
if (!TryResolveScriptFieldType(model, fieldName, fieldType)) {
return false;
}
::XCEngine::Scripting::ScriptFieldValue fieldValue = {};
if (!TryResolveScriptFieldValue(mutation, fieldType, fieldValue)) {
return false;
}
std::vector<::XCEngine::Scripting::ScriptFieldWriteResult> results = {};
return ::XCEngine::Scripting::ScriptEngine::Get().ApplyScriptFieldWrites(
&component,
{ ::XCEngine::Scripting::ScriptFieldWriteRequest{
.fieldName = fieldName,
.type = fieldType,
.value = std::move(fieldValue) } },
results);
}
Component* ResolveComponent(
const GameObject& gameObject,
std::string_view componentId) {
@@ -2211,6 +2500,10 @@ public:
volumeRenderer != nullptr) {
return ApplyVolumeRendererComponentMutation(*volumeRenderer, mutation);
}
if (auto* scriptComponent = dynamic_cast<ScriptComponent*>(component);
scriptComponent != nullptr) {
return ApplyScriptComponentMutation(*scriptComponent, mutation);
}
return false;
}