946 lines
30 KiB
C++
946 lines
30 KiB
C++
#include "Scripting/ScriptEngine.h"
|
|
|
|
#include "Components/GameObject.h"
|
|
#include "Scene/Scene.h"
|
|
#include "Scripting/ScriptComponent.h"
|
|
|
|
#include <algorithm>
|
|
|
|
namespace XCEngine {
|
|
namespace Scripting {
|
|
|
|
namespace {
|
|
|
|
std::unordered_map<std::string, ScriptFieldValue> CollectClassDefaultValues(
|
|
const IScriptRuntime* runtime,
|
|
const ScriptComponent* component,
|
|
ScriptFieldClassStatus classStatus) {
|
|
std::unordered_map<std::string, ScriptFieldValue> defaultValues;
|
|
if (!runtime || !component || classStatus != ScriptFieldClassStatus::Available || !component->HasScriptClass()) {
|
|
return defaultValues;
|
|
}
|
|
|
|
std::vector<ScriptFieldDefaultValue> fields;
|
|
if (!runtime->TryGetClassFieldDefaultValues(
|
|
component->GetAssemblyName(),
|
|
component->GetNamespaceName(),
|
|
component->GetClassName(),
|
|
fields)) {
|
|
return defaultValues;
|
|
}
|
|
|
|
defaultValues.reserve(fields.size());
|
|
for (const ScriptFieldDefaultValue& field : fields) {
|
|
if (field.fieldName.empty() || !IsScriptFieldValueCompatible(field.type, field.value)) {
|
|
continue;
|
|
}
|
|
|
|
defaultValues.emplace(field.fieldName, field.value);
|
|
}
|
|
|
|
return defaultValues;
|
|
}
|
|
|
|
ScriptFieldValue ResolveScriptFieldDefaultValue(
|
|
const ScriptFieldMetadata& metadata,
|
|
const std::unordered_map<std::string, ScriptFieldValue>& defaultValues) {
|
|
const auto it = defaultValues.find(metadata.name);
|
|
if (it != defaultValues.end() && IsScriptFieldValueCompatible(metadata.type, it->second)) {
|
|
return it->second;
|
|
}
|
|
|
|
return CreateDefaultScriptFieldValue(metadata.type);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ScriptEngine& ScriptEngine::Get() {
|
|
static ScriptEngine instance;
|
|
return instance;
|
|
}
|
|
|
|
void ScriptEngine::SetRuntime(IScriptRuntime* runtime) {
|
|
m_runtime = runtime ? runtime : &m_nullRuntime;
|
|
}
|
|
|
|
void ScriptEngine::SetRuntimeFixedDeltaTime(float fixedDeltaTime) {
|
|
if (fixedDeltaTime > 0.0f) {
|
|
m_runtimeFixedDeltaTime = fixedDeltaTime;
|
|
return;
|
|
}
|
|
|
|
m_runtimeFixedDeltaTime = DefaultFixedDeltaTime;
|
|
}
|
|
|
|
void ScriptEngine::OnRuntimeStart(Components::Scene* scene) {
|
|
const float configuredFixedDeltaTime = m_runtimeFixedDeltaTime;
|
|
Physics::PhysicsWorld* configuredPhysicsWorld = m_runtimePhysicsWorld;
|
|
OnRuntimeStop();
|
|
m_runtimeFixedDeltaTime = configuredFixedDeltaTime;
|
|
|
|
if (!scene) {
|
|
return;
|
|
}
|
|
|
|
m_runtimePhysicsWorld = configuredPhysicsWorld;
|
|
m_runtimeScene = scene;
|
|
m_runtimeRunning = true;
|
|
m_runtime->OnRuntimeStart(scene);
|
|
m_runtimeSceneCreatedSubscription = scene->OnGameObjectCreated().Subscribe(
|
|
[this](Components::GameObject* gameObject) {
|
|
HandleGameObjectCreated(gameObject);
|
|
});
|
|
|
|
for (Components::GameObject* root : scene->GetRootGameObjects()) {
|
|
CollectScriptComponents(root);
|
|
}
|
|
|
|
const std::vector<ScriptInstanceKey> startupKeys = m_scriptOrder;
|
|
for (const ScriptInstanceKey& key : startupKeys) {
|
|
auto it = m_scriptStates.find(key);
|
|
if (it == m_scriptStates.end()) {
|
|
continue;
|
|
}
|
|
|
|
ScriptInstanceState& state = it->second;
|
|
if (!ShouldScriptRun(state)) {
|
|
continue;
|
|
}
|
|
|
|
EnsureScriptReady(state, true);
|
|
}
|
|
}
|
|
|
|
void ScriptEngine::OnRuntimeStop() {
|
|
if (m_runtimeScene && m_runtimeSceneCreatedSubscription != 0) {
|
|
m_runtimeScene->OnGameObjectCreated().Unsubscribe(m_runtimeSceneCreatedSubscription);
|
|
m_runtimeScene->OnGameObjectCreated().ProcessUnsubscribes();
|
|
m_runtimeSceneCreatedSubscription = 0;
|
|
}
|
|
|
|
if (!m_runtimeRunning) {
|
|
m_runtimeScene = nullptr;
|
|
m_runtimePhysicsWorld = nullptr;
|
|
m_scriptStates.clear();
|
|
m_scriptOrder.clear();
|
|
m_runtimeFixedDeltaTime = DefaultFixedDeltaTime;
|
|
return;
|
|
}
|
|
|
|
std::vector<ScriptInstanceKey> keys = m_scriptOrder;
|
|
for (const ScriptInstanceKey& key : keys) {
|
|
auto it = m_scriptStates.find(key);
|
|
if (it != m_scriptStates.end()) {
|
|
StopTrackingScript(it->second, true);
|
|
}
|
|
}
|
|
|
|
Components::Scene* stoppedScene = m_runtimeScene;
|
|
m_scriptStates.clear();
|
|
m_scriptOrder.clear();
|
|
m_runtimeRunning = false;
|
|
m_runtimeScene = nullptr;
|
|
m_runtimePhysicsWorld = nullptr;
|
|
m_runtimeFixedDeltaTime = DefaultFixedDeltaTime;
|
|
m_runtime->OnRuntimeStop(stoppedScene);
|
|
}
|
|
|
|
void ScriptEngine::OnRuntimeSceneReplaced(Components::Scene* scene) {
|
|
if (!m_runtimeRunning) {
|
|
m_runtimeScene = nullptr;
|
|
m_runtimePhysicsWorld = nullptr;
|
|
m_runtimeSceneCreatedSubscription = 0;
|
|
m_scriptStates.clear();
|
|
m_scriptOrder.clear();
|
|
return;
|
|
}
|
|
|
|
const float configuredFixedDeltaTime = m_runtimeFixedDeltaTime;
|
|
m_runtime->OnRuntimeStop(nullptr);
|
|
m_runtimeFixedDeltaTime = configuredFixedDeltaTime;
|
|
|
|
m_runtimeScene = scene;
|
|
m_runtimeSceneCreatedSubscription = 0;
|
|
m_scriptStates.clear();
|
|
m_scriptOrder.clear();
|
|
|
|
if (!m_runtimeScene) {
|
|
return;
|
|
}
|
|
|
|
m_runtime->OnRuntimeStart(m_runtimeScene);
|
|
m_runtimeSceneCreatedSubscription = m_runtimeScene->OnGameObjectCreated().Subscribe(
|
|
[this](Components::GameObject* gameObject) {
|
|
HandleGameObjectCreated(gameObject);
|
|
});
|
|
|
|
for (Components::GameObject* root : m_runtimeScene->GetRootGameObjects()) {
|
|
CollectScriptComponents(root);
|
|
}
|
|
|
|
const std::vector<ScriptInstanceKey> startupKeys = m_scriptOrder;
|
|
for (const ScriptInstanceKey& key : startupKeys) {
|
|
auto it = m_scriptStates.find(key);
|
|
if (it == m_scriptStates.end()) {
|
|
continue;
|
|
}
|
|
|
|
ScriptInstanceState& state = it->second;
|
|
if (!ShouldScriptRun(state)) {
|
|
continue;
|
|
}
|
|
|
|
EnsureScriptReady(state, true);
|
|
}
|
|
}
|
|
|
|
void ScriptEngine::OnFixedUpdate(float fixedDeltaTime) {
|
|
if (!m_runtimeRunning) {
|
|
return;
|
|
}
|
|
|
|
const std::vector<ScriptInstanceKey> updateKeys = m_scriptOrder;
|
|
for (const ScriptInstanceKey& key : updateKeys) {
|
|
auto it = m_scriptStates.find(key);
|
|
if (it == m_scriptStates.end()) {
|
|
continue;
|
|
}
|
|
|
|
ScriptInstanceState& state = it->second;
|
|
if (!ShouldScriptRun(state) || !EnsureScriptReady(state, true) || !state.enabled) {
|
|
continue;
|
|
}
|
|
|
|
InvokeLifecycleMethod(state, ScriptLifecycleMethod::FixedUpdate, fixedDeltaTime);
|
|
}
|
|
}
|
|
|
|
void ScriptEngine::OnUpdate(float deltaTime) {
|
|
if (!m_runtimeRunning) {
|
|
return;
|
|
}
|
|
|
|
const std::vector<ScriptInstanceKey> updateKeys = m_scriptOrder;
|
|
for (const ScriptInstanceKey& key : updateKeys) {
|
|
auto it = m_scriptStates.find(key);
|
|
if (it == m_scriptStates.end()) {
|
|
continue;
|
|
}
|
|
|
|
ScriptInstanceState& state = it->second;
|
|
if (!ShouldScriptRun(state) || !EnsureScriptReady(state, true) || !state.enabled) {
|
|
continue;
|
|
}
|
|
|
|
if (state.startPending && !state.startCalled) {
|
|
InvokeLifecycleMethod(state, ScriptLifecycleMethod::Start);
|
|
state.startCalled = true;
|
|
state.startPending = false;
|
|
}
|
|
|
|
InvokeLifecycleMethod(state, ScriptLifecycleMethod::Update, deltaTime);
|
|
}
|
|
}
|
|
|
|
void ScriptEngine::OnLateUpdate(float deltaTime) {
|
|
if (!m_runtimeRunning) {
|
|
return;
|
|
}
|
|
|
|
const std::vector<ScriptInstanceKey> updateKeys = m_scriptOrder;
|
|
for (const ScriptInstanceKey& key : updateKeys) {
|
|
auto it = m_scriptStates.find(key);
|
|
if (it == m_scriptStates.end()) {
|
|
continue;
|
|
}
|
|
|
|
ScriptInstanceState& state = it->second;
|
|
if (!ShouldScriptRun(state) || !EnsureScriptReady(state, true) || !state.enabled) {
|
|
continue;
|
|
}
|
|
|
|
InvokeLifecycleMethod(state, ScriptLifecycleMethod::LateUpdate, deltaTime);
|
|
}
|
|
}
|
|
|
|
void ScriptEngine::DispatchPhysicsMessage(
|
|
Components::GameObject* gameObject,
|
|
ScriptPhysicsMessage message,
|
|
Components::GameObject* other) {
|
|
if (!m_runtimeRunning || !gameObject) {
|
|
return;
|
|
}
|
|
|
|
for (ScriptComponent* component : gameObject->GetComponents<ScriptComponent>()) {
|
|
if (!component) {
|
|
continue;
|
|
}
|
|
|
|
ScriptInstanceState* state = FindState(component);
|
|
if (!state || !ShouldScriptRun(*state) || !EnsureScriptReady(*state, true) || !state->enabled) {
|
|
continue;
|
|
}
|
|
|
|
InvokePhysicsMessage(*state, message, other);
|
|
}
|
|
}
|
|
|
|
void ScriptEngine::OnScriptComponentEnabled(ScriptComponent* component) {
|
|
if (!m_runtimeRunning || !component) {
|
|
return;
|
|
}
|
|
|
|
ScriptInstanceState* state = TrackScriptComponent(component);
|
|
if (!state) {
|
|
return;
|
|
}
|
|
|
|
if (ShouldScriptRun(*state)) {
|
|
EnsureScriptReady(*state, true);
|
|
}
|
|
}
|
|
|
|
void ScriptEngine::OnScriptComponentDisabled(ScriptComponent* component) {
|
|
if (!m_runtimeRunning || !component) {
|
|
return;
|
|
}
|
|
|
|
ScriptInstanceState* state = FindState(component);
|
|
if (!state || !state->enabled) {
|
|
return;
|
|
}
|
|
|
|
InvokeLifecycleMethod(*state, ScriptLifecycleMethod::OnDisable);
|
|
state->enabled = false;
|
|
}
|
|
|
|
void ScriptEngine::OnScriptComponentDestroyed(ScriptComponent* component) {
|
|
if (!m_runtimeRunning || !component) {
|
|
return;
|
|
}
|
|
|
|
ScriptInstanceState* state = FindState(component);
|
|
if (!state) {
|
|
return;
|
|
}
|
|
|
|
StopTrackingScript(*state, false);
|
|
}
|
|
|
|
void ScriptEngine::OnScriptComponentClassChanged(ScriptComponent* component) {
|
|
if (!component) {
|
|
return;
|
|
}
|
|
|
|
if (!m_runtimeRunning) {
|
|
return;
|
|
}
|
|
|
|
if (ScriptInstanceState* state = FindState(component)) {
|
|
StopTrackingScript(*state, false);
|
|
}
|
|
|
|
if (!component->HasScriptClass()) {
|
|
return;
|
|
}
|
|
|
|
ScriptInstanceState* state = TrackScriptComponent(component);
|
|
if (!state) {
|
|
return;
|
|
}
|
|
|
|
if (ShouldScriptRun(*state)) {
|
|
EnsureScriptReady(*state, true);
|
|
}
|
|
}
|
|
|
|
bool ScriptEngine::HasTrackedScriptComponent(const ScriptComponent* component) const {
|
|
return FindState(component) != nullptr;
|
|
}
|
|
|
|
bool ScriptEngine::HasRuntimeInstance(const ScriptComponent* component) const {
|
|
const ScriptInstanceState* state = FindState(component);
|
|
return state && state->instanceCreated;
|
|
}
|
|
|
|
bool ScriptEngine::TryGetAvailableScriptClasses(
|
|
std::vector<ScriptClassDescriptor>& outClasses,
|
|
const std::string& assemblyName) const {
|
|
outClasses.clear();
|
|
|
|
std::vector<ScriptClassDescriptor> runtimeClasses;
|
|
if (!m_runtime->TryGetAvailableScriptClasses(runtimeClasses)) {
|
|
return false;
|
|
}
|
|
|
|
outClasses.reserve(runtimeClasses.size());
|
|
for (const ScriptClassDescriptor& descriptor : runtimeClasses) {
|
|
if (!assemblyName.empty() && descriptor.assemblyName != assemblyName) {
|
|
continue;
|
|
}
|
|
|
|
if (descriptor.className.empty()) {
|
|
continue;
|
|
}
|
|
|
|
outClasses.push_back(descriptor);
|
|
}
|
|
|
|
std::sort(
|
|
outClasses.begin(),
|
|
outClasses.end(),
|
|
[](const ScriptClassDescriptor& lhs, const ScriptClassDescriptor& rhs) {
|
|
if (lhs.assemblyName != rhs.assemblyName) {
|
|
return lhs.assemblyName < rhs.assemblyName;
|
|
}
|
|
if (lhs.namespaceName != rhs.namespaceName) {
|
|
return lhs.namespaceName < rhs.namespaceName;
|
|
}
|
|
return lhs.className < rhs.className;
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ScriptEngine::TrySetScriptFieldValue(
|
|
ScriptComponent* component,
|
|
const std::string& fieldName,
|
|
ScriptFieldType type,
|
|
const ScriptFieldValue& value) {
|
|
if (!component || fieldName.empty() || !IsScriptFieldValueCompatible(type, value)) {
|
|
return false;
|
|
}
|
|
|
|
if (component->HasScriptClass()) {
|
|
std::vector<ScriptFieldMetadata> fields;
|
|
if (m_runtime->TryGetClassFieldMetadata(
|
|
component->GetAssemblyName(),
|
|
component->GetNamespaceName(),
|
|
component->GetClassName(),
|
|
fields)) {
|
|
const auto fieldIt = std::find_if(
|
|
fields.begin(),
|
|
fields.end(),
|
|
[&fieldName](const ScriptFieldMetadata& metadata) {
|
|
return metadata.name == fieldName;
|
|
});
|
|
if (fieldIt == fields.end() || fieldIt->type != type) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
ScriptInstanceState* state = m_runtimeRunning ? FindState(component) : nullptr;
|
|
if (state && state->instanceCreated && !m_runtime->TrySetManagedFieldValue(state->context, fieldName, value)) {
|
|
return false;
|
|
}
|
|
|
|
return component->GetFieldStorage().SetFieldValue(fieldName, type, value);
|
|
}
|
|
|
|
bool ScriptEngine::TryGetScriptFieldValue(
|
|
const ScriptComponent* component,
|
|
const std::string& fieldName,
|
|
ScriptFieldValue& outValue) const {
|
|
if (!component || fieldName.empty()) {
|
|
return false;
|
|
}
|
|
|
|
const ScriptInstanceState* state = m_runtimeRunning ? FindState(component) : nullptr;
|
|
if (state && state->instanceCreated && m_runtime->TryGetManagedFieldValue(state->context, fieldName, outValue)) {
|
|
return true;
|
|
}
|
|
|
|
const StoredScriptField* storedField = component->GetFieldStorage().FindField(fieldName);
|
|
if (!storedField) {
|
|
return false;
|
|
}
|
|
|
|
outValue = storedField->value;
|
|
return true;
|
|
}
|
|
|
|
bool ScriptEngine::ApplyScriptFieldWrites(
|
|
ScriptComponent* component,
|
|
const std::vector<ScriptFieldWriteRequest>& requests,
|
|
std::vector<ScriptFieldWriteResult>& outResults) {
|
|
outResults.clear();
|
|
if (!component) {
|
|
return false;
|
|
}
|
|
|
|
ScriptFieldModel model;
|
|
if (!TryGetScriptFieldModel(component, model)) {
|
|
return false;
|
|
}
|
|
|
|
const std::unordered_map<std::string, ScriptFieldValue> defaultValues =
|
|
CollectClassDefaultValues(m_runtime, component, model.classStatus);
|
|
|
|
std::unordered_map<std::string, const ScriptFieldSnapshot*> fieldByName;
|
|
fieldByName.reserve(model.fields.size());
|
|
for (const ScriptFieldSnapshot& field : model.fields) {
|
|
fieldByName.emplace(field.metadata.name, &field);
|
|
}
|
|
|
|
outResults.reserve(requests.size());
|
|
|
|
bool allApplied = true;
|
|
for (const ScriptFieldWriteRequest& request : requests) {
|
|
ScriptFieldWriteResult result;
|
|
result.fieldName = request.fieldName;
|
|
result.type = request.type;
|
|
|
|
if (request.fieldName.empty()) {
|
|
result.status = ScriptFieldWriteStatus::EmptyFieldName;
|
|
allApplied = false;
|
|
outResults.push_back(std::move(result));
|
|
continue;
|
|
}
|
|
|
|
if (!IsScriptFieldValueCompatible(request.type, request.value)) {
|
|
result.status = ScriptFieldWriteStatus::InvalidValue;
|
|
allApplied = false;
|
|
outResults.push_back(std::move(result));
|
|
continue;
|
|
}
|
|
|
|
const auto fieldIt = fieldByName.find(request.fieldName);
|
|
if (fieldIt == fieldByName.end()) {
|
|
result.status = ScriptFieldWriteStatus::UnknownField;
|
|
allApplied = false;
|
|
outResults.push_back(std::move(result));
|
|
continue;
|
|
}
|
|
|
|
const ScriptFieldSnapshot& field = *fieldIt->second;
|
|
if (field.metadata.type != request.type) {
|
|
result.status = ScriptFieldWriteStatus::TypeMismatch;
|
|
allApplied = false;
|
|
outResults.push_back(std::move(result));
|
|
continue;
|
|
}
|
|
|
|
if (model.classStatus == ScriptFieldClassStatus::Available && !field.declaredInClass) {
|
|
result.status = ScriptFieldWriteStatus::StoredOnlyField;
|
|
allApplied = false;
|
|
outResults.push_back(std::move(result));
|
|
continue;
|
|
}
|
|
|
|
const bool applied = field.declaredInClass
|
|
? TrySetScriptFieldValue(component, request.fieldName, request.type, request.value)
|
|
: component->GetFieldStorage().SetFieldValue(request.fieldName, request.type, request.value);
|
|
if (!applied) {
|
|
result.status = ScriptFieldWriteStatus::ApplyFailed;
|
|
allApplied = false;
|
|
outResults.push_back(std::move(result));
|
|
continue;
|
|
}
|
|
|
|
result.status = ScriptFieldWriteStatus::Applied;
|
|
outResults.push_back(std::move(result));
|
|
}
|
|
|
|
return allApplied;
|
|
}
|
|
|
|
bool ScriptEngine::ClearScriptFieldOverrides(
|
|
ScriptComponent* component,
|
|
const std::vector<ScriptFieldClearRequest>& requests,
|
|
std::vector<ScriptFieldClearResult>& outResults) {
|
|
outResults.clear();
|
|
if (!component) {
|
|
return false;
|
|
}
|
|
|
|
ScriptFieldModel model;
|
|
if (!TryGetScriptFieldModel(component, model)) {
|
|
return false;
|
|
}
|
|
|
|
const std::unordered_map<std::string, ScriptFieldValue> defaultValues =
|
|
CollectClassDefaultValues(m_runtime, component, model.classStatus);
|
|
|
|
std::unordered_map<std::string, const ScriptFieldSnapshot*> fieldByName;
|
|
fieldByName.reserve(model.fields.size());
|
|
for (const ScriptFieldSnapshot& field : model.fields) {
|
|
fieldByName.emplace(field.metadata.name, &field);
|
|
}
|
|
|
|
ScriptInstanceState* state = m_runtimeRunning ? FindState(component) : nullptr;
|
|
const bool hasLiveInstance = state && state->instanceCreated;
|
|
|
|
outResults.reserve(requests.size());
|
|
|
|
bool allApplied = true;
|
|
for (const ScriptFieldClearRequest& request : requests) {
|
|
ScriptFieldClearResult result;
|
|
result.fieldName = request.fieldName;
|
|
|
|
if (request.fieldName.empty()) {
|
|
result.status = ScriptFieldClearStatus::EmptyFieldName;
|
|
allApplied = false;
|
|
outResults.push_back(std::move(result));
|
|
continue;
|
|
}
|
|
|
|
const auto fieldIt = fieldByName.find(request.fieldName);
|
|
if (fieldIt == fieldByName.end()) {
|
|
result.status = ScriptFieldClearStatus::UnknownField;
|
|
allApplied = false;
|
|
outResults.push_back(std::move(result));
|
|
continue;
|
|
}
|
|
|
|
const ScriptFieldSnapshot& field = *fieldIt->second;
|
|
|
|
bool resetManagedValue = false;
|
|
if (field.declaredInClass && hasLiveInstance) {
|
|
if (!m_runtime->TrySetManagedFieldValue(
|
|
state->context,
|
|
field.metadata.name,
|
|
ResolveScriptFieldDefaultValue(field.metadata, defaultValues))) {
|
|
result.status = ScriptFieldClearStatus::ApplyFailed;
|
|
allApplied = false;
|
|
outResults.push_back(std::move(result));
|
|
continue;
|
|
}
|
|
|
|
resetManagedValue = true;
|
|
}
|
|
|
|
bool removedStoredValue = false;
|
|
if (field.hasStoredValue) {
|
|
removedStoredValue = component->GetFieldStorage().Remove(field.metadata.name);
|
|
if (!removedStoredValue) {
|
|
result.status = ScriptFieldClearStatus::ApplyFailed;
|
|
allApplied = false;
|
|
outResults.push_back(std::move(result));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!removedStoredValue && !resetManagedValue) {
|
|
result.status = ScriptFieldClearStatus::NoValueToClear;
|
|
allApplied = false;
|
|
outResults.push_back(std::move(result));
|
|
continue;
|
|
}
|
|
|
|
result.status = ScriptFieldClearStatus::Applied;
|
|
outResults.push_back(std::move(result));
|
|
}
|
|
|
|
return allApplied;
|
|
}
|
|
|
|
bool ScriptEngine::TryGetScriptFieldModel(
|
|
const ScriptComponent* component,
|
|
ScriptFieldModel& outModel) const {
|
|
outModel = ScriptFieldModel{};
|
|
if (!component) {
|
|
return false;
|
|
}
|
|
|
|
std::vector<ScriptFieldMetadata> metadataFields;
|
|
if (!component->HasScriptClass()) {
|
|
outModel.classStatus = ScriptFieldClassStatus::Unassigned;
|
|
} else if (m_runtime->TryGetClassFieldMetadata(
|
|
component->GetAssemblyName(),
|
|
component->GetNamespaceName(),
|
|
component->GetClassName(),
|
|
metadataFields)) {
|
|
outModel.classStatus = ScriptFieldClassStatus::Available;
|
|
} else {
|
|
outModel.classStatus = ScriptFieldClassStatus::Missing;
|
|
}
|
|
|
|
const std::unordered_map<std::string, ScriptFieldValue> defaultValues =
|
|
CollectClassDefaultValues(m_runtime, component, outModel.classStatus);
|
|
|
|
const ScriptInstanceState* state = m_runtimeRunning ? FindState(component) : nullptr;
|
|
std::unordered_map<std::string, size_t> fieldIndexByName;
|
|
fieldIndexByName.reserve(metadataFields.size());
|
|
|
|
for (const ScriptFieldMetadata& metadata : metadataFields) {
|
|
ScriptFieldSnapshot snapshot;
|
|
snapshot.metadata = metadata;
|
|
snapshot.declaredInClass = true;
|
|
snapshot.hasDefaultValue = true;
|
|
snapshot.defaultValue = ResolveScriptFieldDefaultValue(metadata, defaultValues);
|
|
snapshot.value = snapshot.defaultValue;
|
|
snapshot.valueSource = ScriptFieldValueSource::DefaultValue;
|
|
|
|
const StoredScriptField* storedField = component->GetFieldStorage().FindField(metadata.name);
|
|
if (storedField) {
|
|
snapshot.hasStoredValue = true;
|
|
snapshot.storedType = storedField->type;
|
|
snapshot.storedValue = storedField->value;
|
|
if (storedField->type != metadata.type) {
|
|
snapshot.issue = ScriptFieldIssue::TypeMismatch;
|
|
}
|
|
}
|
|
|
|
if (state && state->instanceCreated && m_runtime->TryGetManagedFieldValue(state->context, metadata.name, snapshot.value)) {
|
|
snapshot.hasValue = true;
|
|
snapshot.valueSource = ScriptFieldValueSource::ManagedValue;
|
|
} else if (storedField && storedField->type == metadata.type) {
|
|
snapshot.hasValue = true;
|
|
snapshot.value = storedField->value;
|
|
snapshot.valueSource = ScriptFieldValueSource::StoredValue;
|
|
}
|
|
|
|
fieldIndexByName.emplace(metadata.name, outModel.fields.size());
|
|
outModel.fields.push_back(std::move(snapshot));
|
|
}
|
|
|
|
for (const std::string& fieldName : component->GetFieldStorage().GetFieldNames()) {
|
|
const StoredScriptField* storedField = component->GetFieldStorage().FindField(fieldName);
|
|
if (!storedField) {
|
|
continue;
|
|
}
|
|
|
|
const auto fieldIndexIt = fieldIndexByName.find(fieldName);
|
|
if (fieldIndexIt != fieldIndexByName.end()) {
|
|
continue;
|
|
}
|
|
|
|
ScriptFieldSnapshot snapshot;
|
|
snapshot.metadata = ScriptFieldMetadata{fieldName, storedField->type};
|
|
snapshot.hasValue = true;
|
|
snapshot.value = storedField->value;
|
|
snapshot.valueSource = ScriptFieldValueSource::StoredValue;
|
|
snapshot.issue = ScriptFieldIssue::StoredOnly;
|
|
snapshot.hasStoredValue = true;
|
|
snapshot.storedType = storedField->type;
|
|
snapshot.storedValue = storedField->value;
|
|
outModel.fields.push_back(std::move(snapshot));
|
|
}
|
|
|
|
if (outModel.classStatus != ScriptFieldClassStatus::Available) {
|
|
std::sort(
|
|
outModel.fields.begin(),
|
|
outModel.fields.end(),
|
|
[](const ScriptFieldSnapshot& lhs, const ScriptFieldSnapshot& rhs) {
|
|
return lhs.metadata.name < rhs.metadata.name;
|
|
});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ScriptEngine::TryGetScriptFieldSnapshots(
|
|
const ScriptComponent* component,
|
|
std::vector<ScriptFieldSnapshot>& outFields) const {
|
|
ScriptFieldModel model;
|
|
if (!TryGetScriptFieldModel(component, model)) {
|
|
outFields.clear();
|
|
return false;
|
|
}
|
|
outFields = std::move(model.fields);
|
|
return !outFields.empty();
|
|
}
|
|
|
|
size_t ScriptEngine::ScriptInstanceKeyHasher::operator()(const ScriptInstanceKey& key) const {
|
|
const size_t h1 = std::hash<uint64_t>{}(key.gameObjectUUID);
|
|
const size_t h2 = std::hash<uint64_t>{}(key.scriptComponentUUID);
|
|
return h1 ^ (h2 + 0x9e3779b97f4a7c15ULL + (h1 << 6) + (h1 >> 2));
|
|
}
|
|
|
|
void ScriptEngine::CollectScriptComponents(Components::GameObject* gameObject) {
|
|
if (!gameObject) {
|
|
return;
|
|
}
|
|
|
|
for (ScriptComponent* component : gameObject->GetComponents<ScriptComponent>()) {
|
|
TrackScriptComponent(component);
|
|
}
|
|
|
|
for (Components::GameObject* child : gameObject->GetChildren()) {
|
|
CollectScriptComponents(child);
|
|
}
|
|
}
|
|
|
|
void ScriptEngine::EnsureTrackedScriptsReady(Components::GameObject* gameObject) {
|
|
if (!gameObject) {
|
|
return;
|
|
}
|
|
|
|
for (ScriptComponent* component : gameObject->GetComponents<ScriptComponent>()) {
|
|
ScriptInstanceState* state = FindState(component);
|
|
if (!state || !ShouldScriptRun(*state)) {
|
|
continue;
|
|
}
|
|
|
|
EnsureScriptReady(*state, true);
|
|
}
|
|
|
|
for (Components::GameObject* child : gameObject->GetChildren()) {
|
|
EnsureTrackedScriptsReady(child);
|
|
}
|
|
}
|
|
|
|
void ScriptEngine::HandleGameObjectCreated(Components::GameObject* gameObject) {
|
|
if (!m_runtimeRunning || !gameObject || gameObject->GetScene() != m_runtimeScene) {
|
|
return;
|
|
}
|
|
|
|
CollectScriptComponents(gameObject);
|
|
EnsureTrackedScriptsReady(gameObject);
|
|
}
|
|
|
|
ScriptEngine::ScriptInstanceState* ScriptEngine::TrackScriptComponent(ScriptComponent* component) {
|
|
if (!component || !component->GetGameObject()) {
|
|
return nullptr;
|
|
}
|
|
|
|
const ScriptInstanceKey key{
|
|
component->GetGameObject()->GetUUID(),
|
|
component->GetScriptComponentUUID()
|
|
};
|
|
|
|
auto it = m_scriptStates.find(key);
|
|
if (it != m_scriptStates.end()) {
|
|
it->second.context.scene = component->GetScene();
|
|
it->second.context.gameObject = component->GetGameObject();
|
|
it->second.context.component = component;
|
|
it->second.context.gameObjectUUID = component->GetGameObject()->GetUUID();
|
|
it->second.context.scriptComponentUUID = component->GetScriptComponentUUID();
|
|
return &it->second;
|
|
}
|
|
|
|
ScriptInstanceState state;
|
|
state.context.scene = component->GetScene();
|
|
state.context.gameObject = component->GetGameObject();
|
|
state.context.component = component;
|
|
state.context.gameObjectUUID = component->GetGameObject()->GetUUID();
|
|
state.context.scriptComponentUUID = component->GetScriptComponentUUID();
|
|
|
|
auto [insertedIt, inserted] = m_scriptStates.emplace(key, std::move(state));
|
|
if (inserted) {
|
|
m_scriptOrder.push_back(key);
|
|
}
|
|
|
|
return &insertedIt->second;
|
|
}
|
|
|
|
ScriptEngine::ScriptInstanceState* ScriptEngine::FindState(const ScriptComponent* component) {
|
|
if (!component || !component->GetGameObject()) {
|
|
return nullptr;
|
|
}
|
|
|
|
const ScriptInstanceKey key{
|
|
component->GetGameObject()->GetUUID(),
|
|
component->GetScriptComponentUUID()
|
|
};
|
|
|
|
auto it = m_scriptStates.find(key);
|
|
return it != m_scriptStates.end() ? &it->second : nullptr;
|
|
}
|
|
|
|
const ScriptEngine::ScriptInstanceState* ScriptEngine::FindState(const ScriptComponent* component) const {
|
|
if (!component || !component->GetGameObject()) {
|
|
return nullptr;
|
|
}
|
|
|
|
const ScriptInstanceKey key{
|
|
component->GetGameObject()->GetUUID(),
|
|
component->GetScriptComponentUUID()
|
|
};
|
|
|
|
auto it = m_scriptStates.find(key);
|
|
return it != m_scriptStates.end() ? &it->second : nullptr;
|
|
}
|
|
|
|
void ScriptEngine::RemoveTrackedScriptComponent(const ScriptComponent* component) {
|
|
if (!component || !component->GetGameObject()) {
|
|
return;
|
|
}
|
|
|
|
const ScriptInstanceKey key{
|
|
component->GetGameObject()->GetUUID(),
|
|
component->GetScriptComponentUUID()
|
|
};
|
|
|
|
m_scriptStates.erase(key);
|
|
m_scriptOrder.erase(
|
|
std::remove(m_scriptOrder.begin(), m_scriptOrder.end(), key),
|
|
m_scriptOrder.end());
|
|
}
|
|
|
|
bool ScriptEngine::ShouldScriptRun(const ScriptInstanceState& state) const {
|
|
return m_runtimeRunning
|
|
&& state.context.scene == m_runtimeScene
|
|
&& state.context.scene != nullptr
|
|
&& state.context.scene->IsActive()
|
|
&& state.context.gameObject != nullptr
|
|
&& state.context.gameObject->IsActiveInHierarchy()
|
|
&& state.context.component != nullptr
|
|
&& state.context.component->IsEnabled()
|
|
&& state.context.component->HasScriptClass();
|
|
}
|
|
|
|
bool ScriptEngine::EnsureScriptReady(ScriptInstanceState& state, bool invokeEnableIfNeeded) {
|
|
if (!state.context.component || !state.context.gameObject || !state.context.scene || !state.context.component->HasScriptClass()) {
|
|
return false;
|
|
}
|
|
|
|
if (!state.instanceCreated) {
|
|
if (!m_runtime->CreateScriptInstance(state.context)) {
|
|
return false;
|
|
}
|
|
state.instanceCreated = true;
|
|
}
|
|
|
|
if (!state.awakeCalled) {
|
|
InvokeLifecycleMethod(state, ScriptLifecycleMethod::Awake);
|
|
state.awakeCalled = true;
|
|
}
|
|
|
|
if (invokeEnableIfNeeded && !state.enabled && ShouldScriptRun(state)) {
|
|
InvokeLifecycleMethod(state, ScriptLifecycleMethod::OnEnable);
|
|
state.enabled = true;
|
|
if (!state.startCalled) {
|
|
state.startPending = true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ScriptEngine::InvokeLifecycleMethod(ScriptInstanceState& state, ScriptLifecycleMethod method, float deltaTime) {
|
|
m_runtime->InvokeMethod(state.context, method, deltaTime);
|
|
m_runtime->SyncManagedFieldsToStorage(state.context);
|
|
}
|
|
|
|
void ScriptEngine::InvokePhysicsMessage(
|
|
ScriptInstanceState& state,
|
|
ScriptPhysicsMessage message,
|
|
Components::GameObject* other) {
|
|
m_runtime->InvokePhysicsMessage(state.context, message, other);
|
|
m_runtime->SyncManagedFieldsToStorage(state.context);
|
|
}
|
|
|
|
void ScriptEngine::StopTrackingScript(ScriptInstanceState& state, bool runtimeStopping) {
|
|
if (state.enabled) {
|
|
InvokeLifecycleMethod(state, ScriptLifecycleMethod::OnDisable);
|
|
state.enabled = false;
|
|
}
|
|
|
|
if (state.instanceCreated) {
|
|
InvokeLifecycleMethod(state, ScriptLifecycleMethod::OnDestroy);
|
|
m_runtime->DestroyScriptInstance(state.context);
|
|
state.instanceCreated = false;
|
|
}
|
|
|
|
state.startPending = false;
|
|
|
|
if (!runtimeStopping) {
|
|
RemoveTrackedScriptComponent(state.context.component);
|
|
}
|
|
}
|
|
|
|
} // namespace Scripting
|
|
} // namespace XCEngine
|