feat(scripting): add field model editing and defaults support

This commit is contained in:
2026-03-28 15:09:42 +08:00
parent 4717b595c4
commit 14c7fd69ec
13 changed files with 2113 additions and 0 deletions

View File

@@ -9,6 +9,51 @@
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;
@@ -203,6 +248,346 @@ bool ScriptEngine::HasRuntimeInstance(const ScriptComponent* component) const {
return state && state->instanceCreated;
}
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);
@@ -372,6 +757,7 @@ bool ScriptEngine::EnsureScriptReady(ScriptInstanceState& state, bool invokeEnab
void ScriptEngine::InvokeLifecycleMethod(ScriptInstanceState& state, ScriptLifecycleMethod method, float deltaTime) {
m_runtime->InvokeMethod(state.context, method, deltaTime);
m_runtime->SyncManagedFieldsToStorage(state.context);
}
void ScriptEngine::StopTrackingScript(ScriptInstanceState& state, bool runtimeStopping) {