Advance editor runtime and scripting integration
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user