feat: expand editor scripting asset and viewport flow

This commit is contained in:
2026-04-03 13:22:30 +08:00
parent ed8c27fde2
commit a05d0b80a2
124 changed files with 10397 additions and 1737 deletions

View File

@@ -20,6 +20,10 @@ bool ShouldTraceMeshPath(const std::string& path) {
path.find("backpack") != std::string::npos;
}
bool HasVirtualPathScheme(const std::string& path) {
return path.find("://") != std::string::npos;
}
std::string EncodeAssetRef(const Resources::AssetRef& assetRef) {
if (!assetRef.IsValid()) {
return std::string();
@@ -67,17 +71,20 @@ struct MeshFilterComponent::PendingMeshLoadState {
};
Resources::Mesh* MeshFilterComponent::GetMesh() const {
const_cast<MeshFilterComponent*>(this)->EnsureDeferredAsyncMeshLoadStarted();
const_cast<MeshFilterComponent*>(this)->ResolvePendingMesh();
return m_mesh.Get();
}
const Resources::ResourceHandle<Resources::Mesh>& MeshFilterComponent::GetMeshHandle() const {
const_cast<MeshFilterComponent*>(this)->EnsureDeferredAsyncMeshLoadStarted();
const_cast<MeshFilterComponent*>(this)->ResolvePendingMesh();
return m_mesh;
}
void MeshFilterComponent::SetMeshPath(const std::string& meshPath) {
m_pendingMeshLoad.reset();
m_asyncMeshLoadRequested = false;
m_meshPath = meshPath;
if (m_meshPath.empty()) {
m_mesh.Reset();
@@ -101,6 +108,7 @@ void MeshFilterComponent::SetMeshPath(const std::string& meshPath) {
void MeshFilterComponent::SetMesh(const Resources::ResourceHandle<Resources::Mesh>& mesh) {
m_pendingMeshLoad.reset();
m_asyncMeshLoadRequested = false;
m_mesh = mesh;
m_meshPath = mesh.Get() != nullptr ? ToStdString(mesh->GetPath()) : std::string();
if (m_meshPath.empty()) {
@@ -116,18 +124,29 @@ void MeshFilterComponent::SetMesh(Resources::Mesh* mesh) {
void MeshFilterComponent::ClearMesh() {
m_pendingMeshLoad.reset();
m_asyncMeshLoadRequested = false;
m_mesh.Reset();
m_meshPath.clear();
m_meshRef.Reset();
}
void MeshFilterComponent::Serialize(std::ostream& os) const {
os << "mesh=" << m_meshPath << ";";
os << "meshRef=" << EncodeAssetRef(m_meshRef) << ";";
Resources::AssetRef meshRef = m_meshRef;
if (!meshRef.IsValid() &&
!m_meshPath.empty() &&
!HasVirtualPathScheme(m_meshPath) &&
Resources::ResourceManager::Get().TryGetAssetRef(m_meshPath.c_str(), Resources::ResourceType::Mesh, meshRef)) {
}
os << "meshRef=" << EncodeAssetRef(meshRef) << ";";
if (!meshRef.IsValid() && !m_meshPath.empty()) {
os << "meshPath=" << m_meshPath << ";";
}
}
void MeshFilterComponent::Deserialize(std::istream& is) {
m_pendingMeshLoad.reset();
m_asyncMeshLoadRequested = false;
m_mesh.Reset();
m_meshPath.clear();
m_meshRef.Reset();
@@ -148,7 +167,7 @@ void MeshFilterComponent::Deserialize(std::istream& is) {
const std::string key = token.substr(0, eqPos);
const std::string value = token.substr(eqPos + 1);
if (key == "mesh") {
if (key == "mesh" || key == "meshPath") {
pendingMeshPath = value;
} else if (key == "meshRef") {
TryDecodeAssetRef(value, pendingMeshRef);
@@ -172,7 +191,6 @@ void MeshFilterComponent::Deserialize(std::istream& is) {
if (ShouldTraceMeshPath(m_meshPath)) {
TraceMeshFilter(*this, std::string("Resolved meshRef to path=") + m_meshPath);
}
BeginAsyncMeshLoad(m_meshPath);
return;
}
@@ -191,7 +209,6 @@ void MeshFilterComponent::Deserialize(std::istream& is) {
if (!Resources::ResourceManager::Get().TryGetAssetRef(m_meshPath.c_str(), Resources::ResourceType::Mesh, m_meshRef)) {
m_meshRef.Reset();
}
BeginAsyncMeshLoad(m_meshPath);
return;
}
@@ -202,10 +219,12 @@ void MeshFilterComponent::Deserialize(std::istream& is) {
void MeshFilterComponent::BeginAsyncMeshLoad(const std::string& meshPath) {
if (meshPath.empty()) {
m_pendingMeshLoad.reset();
m_asyncMeshLoadRequested = false;
m_mesh.Reset();
return;
}
m_asyncMeshLoadRequested = true;
m_mesh.Reset();
m_pendingMeshLoad = std::make_shared<PendingMeshLoadState>();
if (ShouldTraceMeshPath(meshPath)) {
@@ -219,7 +238,15 @@ void MeshFilterComponent::BeginAsyncMeshLoad(const std::string& meshPath) {
state->result = std::move(result);
state->completed = true;
}
});
});
}
void MeshFilterComponent::EnsureDeferredAsyncMeshLoadStarted() {
if (m_asyncMeshLoadRequested || m_mesh.Get() != nullptr || m_meshPath.empty()) {
return;
}
BeginAsyncMeshLoad(m_meshPath);
}
void MeshFilterComponent::ResolvePendingMesh() {

View File

@@ -4,6 +4,7 @@
#include "Core/Asset/ResourceManager.h"
#include "Debug/Logger.h"
#include <algorithm>
#include <sstream>
namespace XCEngine {
@@ -21,6 +22,10 @@ bool ShouldTraceMaterialPath(const std::string& path) {
path.find("New Material.mat") != std::string::npos;
}
bool HasVirtualPathScheme(const std::string& path) {
return path.find("://") != std::string::npos;
}
std::string EncodeAssetRef(const Resources::AssetRef& assetRef) {
if (!assetRef.IsValid()) {
return std::string();
@@ -145,11 +150,13 @@ struct MeshRendererComponent::PendingMaterialLoadState {
};
Resources::Material* MeshRendererComponent::GetMaterial(size_t index) const {
const_cast<MeshRendererComponent*>(this)->EnsureDeferredAsyncMaterialLoadStarted(index);
const_cast<MeshRendererComponent*>(this)->ResolvePendingMaterials();
return index < m_materials.size() ? m_materials[index].Get() : nullptr;
}
const Resources::ResourceHandle<Resources::Material>& MeshRendererComponent::GetMaterialHandle(size_t index) const {
const_cast<MeshRendererComponent*>(this)->EnsureDeferredAsyncMaterialLoadStarted(index);
const_cast<MeshRendererComponent*>(this)->ResolvePendingMaterials();
static const Resources::ResourceHandle<Resources::Material> kNullHandle;
return index < m_materials.size() ? m_materials[index] : kNullHandle;
@@ -163,6 +170,7 @@ const std::string& MeshRendererComponent::GetMaterialPath(size_t index) const {
void MeshRendererComponent::SetMaterialPath(size_t index, const std::string& materialPath) {
EnsureMaterialSlot(index);
m_pendingMaterialLoads[index].reset();
m_asyncMaterialLoadRequested[index] = false;
m_materialPaths[index] = materialPath;
if (materialPath.empty()) {
m_materials[index].Reset();
@@ -188,6 +196,7 @@ void MeshRendererComponent::SetMaterialPath(size_t index, const std::string& mat
void MeshRendererComponent::SetMaterial(size_t index, const Resources::ResourceHandle<Resources::Material>& material) {
EnsureMaterialSlot(index);
m_pendingMaterialLoads[index].reset();
m_asyncMaterialLoadRequested[index] = false;
m_materials[index] = material;
m_materialPaths[index] = MaterialPathFromHandle(material);
if (m_materialPaths[index].empty() ||
@@ -206,6 +215,8 @@ void MeshRendererComponent::SetMaterials(const std::vector<Resources::ResourceHa
m_materialRefs.resize(materials.size());
m_pendingMaterialLoads.clear();
m_pendingMaterialLoads.resize(materials.size());
m_asyncMaterialLoadRequested.clear();
m_asyncMaterialLoadRequested.resize(materials.size(), false);
for (size_t i = 0; i < materials.size(); ++i) {
m_materialPaths[i] = MaterialPathFromHandle(materials[i]);
if (m_materialPaths[i].empty() ||
@@ -220,23 +231,45 @@ void MeshRendererComponent::ClearMaterials() {
m_materialPaths.clear();
m_materialRefs.clear();
m_pendingMaterialLoads.clear();
m_asyncMaterialLoadRequested.clear();
}
void MeshRendererComponent::Serialize(std::ostream& os) const {
os << "materials=";
for (size_t i = 0; i < m_materialPaths.size(); ++i) {
const size_t slotCount = std::max(m_materialPaths.size(), m_materialRefs.size());
std::vector<Resources::AssetRef> serializedRefs = m_materialRefs;
serializedRefs.resize(slotCount);
std::vector<std::string> serializedPaths = m_materialPaths;
serializedPaths.resize(slotCount);
std::vector<std::string> fallbackPaths(slotCount);
for (size_t i = 0; i < slotCount; ++i) {
if (!serializedRefs[i].IsValid() &&
!serializedPaths[i].empty() &&
!HasVirtualPathScheme(serializedPaths[i]) &&
Resources::ResourceManager::Get().TryGetAssetRef(
serializedPaths[i].c_str(),
Resources::ResourceType::Material,
serializedRefs[i])) {
}
if (!serializedRefs[i].IsValid()) {
fallbackPaths[i] = serializedPaths[i];
}
}
os << "materialPaths=";
for (size_t i = 0; i < slotCount; ++i) {
if (i > 0) {
os << "|";
}
os << m_materialPaths[i];
os << fallbackPaths[i];
}
os << ";";
os << "materialRefs=";
for (size_t i = 0; i < m_materialRefs.size(); ++i) {
for (size_t i = 0; i < serializedRefs.size(); ++i) {
if (i > 0) {
os << "|";
}
os << EncodeAssetRef(m_materialRefs[i]);
os << EncodeAssetRef(serializedRefs[i]);
}
os << ";";
os << "castShadows=" << (m_castShadows ? 1 : 0) << ";";
@@ -265,11 +298,12 @@ void MeshRendererComponent::Deserialize(std::istream& is) {
const std::string key = token.substr(0, eqPos);
const std::string value = token.substr(eqPos + 1);
if (key == "materials") {
if (key == "materials" || key == "materialPaths") {
m_materialPaths = SplitMaterialPaths(value);
m_materials.resize(m_materialPaths.size());
m_materialRefs.resize(m_materialPaths.size());
m_pendingMaterialLoads.resize(m_materialPaths.size());
m_asyncMaterialLoadRequested.resize(m_materialPaths.size(), false);
} else if (key == "materialRefs") {
pendingMaterialRefs = SplitMaterialRefs(value);
} else if (key == "castShadows") {
@@ -286,6 +320,7 @@ void MeshRendererComponent::Deserialize(std::istream& is) {
m_materials.resize(pendingMaterialRefs.size());
m_materialRefs.resize(pendingMaterialRefs.size());
m_pendingMaterialLoads.resize(pendingMaterialRefs.size());
m_asyncMaterialLoadRequested.resize(pendingMaterialRefs.size(), false);
}
if (ShouldTraceAnyMaterialPath(m_materialPaths)) {
@@ -310,7 +345,6 @@ void MeshRendererComponent::Deserialize(std::istream& is) {
std::string("Resolved materialRef slot=") + std::to_string(i) +
" path=" + m_materialPaths[i]);
}
BeginAsyncMaterialLoad(i, m_materialPaths[i]);
restoredOrQueued = true;
}
}
@@ -336,7 +370,6 @@ void MeshRendererComponent::Deserialize(std::istream& is) {
m_materialRefs[i])) {
m_materialRefs[i].Reset();
}
BeginAsyncMaterialLoad(i, m_materialPaths[i]);
}
continue;
}
@@ -349,10 +382,12 @@ void MeshRendererComponent::BeginAsyncMaterialLoad(size_t index, const std::stri
EnsureMaterialSlot(index);
if (materialPath.empty()) {
m_pendingMaterialLoads[index].reset();
m_asyncMaterialLoadRequested[index] = false;
m_materials[index].Reset();
return;
}
m_asyncMaterialLoadRequested[index] = true;
m_materials[index].Reset();
m_pendingMaterialLoads[index] = std::make_shared<PendingMaterialLoadState>();
if (ShouldTraceMaterialPath(materialPath)) {
@@ -369,7 +404,22 @@ void MeshRendererComponent::BeginAsyncMaterialLoad(size_t index, const std::stri
state->result = std::move(result);
state->completed = true;
}
});
});
}
void MeshRendererComponent::EnsureDeferredAsyncMaterialLoadStarted(size_t index) {
if (index >= m_materialPaths.size()) {
return;
}
EnsureMaterialSlot(index);
if (m_asyncMaterialLoadRequested[index] ||
m_materials[index].Get() != nullptr ||
m_materialPaths[index].empty()) {
return;
}
BeginAsyncMaterialLoad(index, m_materialPaths[index]);
}
void MeshRendererComponent::ResolvePendingMaterials() {
@@ -428,6 +478,9 @@ void MeshRendererComponent::EnsureMaterialSlot(size_t index) {
if (index >= m_pendingMaterialLoads.size()) {
m_pendingMaterialLoads.resize(index + 1);
}
if (index >= m_asyncMaterialLoadRequested.size()) {
m_asyncMaterialLoadRequested.resize(index + 1, false);
}
}
std::string MeshRendererComponent::MaterialPathFromHandle(const Resources::ResourceHandle<Resources::Material>& material) {

View File

@@ -592,6 +592,28 @@ bool AssetDatabase::TryGetPrimaryAssetPath(const AssetGUID& guid, Containers::St
return true;
}
void AssetDatabase::BuildLookupSnapshot(std::unordered_map<std::string, AssetGUID>& outPathToGuid,
std::unordered_map<AssetGUID, Containers::String>& outGuidToPath) const {
outPathToGuid.clear();
outGuidToPath.clear();
outPathToGuid.reserve(m_sourcesByPathKey.size());
outGuidToPath.reserve(m_sourcesByGuid.size());
for (const auto& [pathKey, record] : m_sourcesByPathKey) {
if (!record.guid.IsValid() || record.relativePath.Empty()) {
continue;
}
outPathToGuid[pathKey] = record.guid;
}
for (const auto& [guid, record] : m_sourcesByGuid) {
if (!guid.IsValid() || record.relativePath.Empty()) {
continue;
}
outGuidToPath[guid] = record.relativePath;
}
}
void AssetDatabase::EnsureProjectLayout() {
std::error_code ec;
fs::create_directories(fs::path(m_assetsRoot.CStr()), ec);

View File

@@ -0,0 +1,88 @@
#include <XCEngine/Core/Asset/AssetImportService.h>
namespace XCEngine {
namespace Resources {
void AssetImportService::Initialize() {
}
void AssetImportService::Shutdown() {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
m_assetDatabase.Shutdown();
m_projectRoot.Clear();
}
void AssetImportService::SetProjectRoot(const Containers::String& projectRoot) {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (m_projectRoot == projectRoot) {
return;
}
if (!m_projectRoot.Empty()) {
m_assetDatabase.Shutdown();
}
m_projectRoot = projectRoot;
if (!m_projectRoot.Empty()) {
m_assetDatabase.Initialize(m_projectRoot);
}
}
Containers::String AssetImportService::GetProjectRoot() const {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
return m_projectRoot;
}
void AssetImportService::Refresh() {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (!m_projectRoot.Empty()) {
m_assetDatabase.Refresh();
}
}
bool AssetImportService::EnsureArtifact(const Containers::String& requestPath,
ResourceType requestedType,
AssetDatabase::ResolvedAsset& outAsset) {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (m_projectRoot.Empty()) {
return false;
}
return m_assetDatabase.EnsureArtifact(requestPath, requestedType, outAsset);
}
bool AssetImportService::TryGetAssetRef(const Containers::String& requestPath,
ResourceType resourceType,
AssetRef& outRef) const {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (m_projectRoot.Empty()) {
return false;
}
return m_assetDatabase.TryGetAssetRef(requestPath, resourceType, outRef);
}
bool AssetImportService::TryGetPrimaryAssetPath(const AssetGUID& guid, Containers::String& outRelativePath) const {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (m_projectRoot.Empty()) {
return false;
}
return m_assetDatabase.TryGetPrimaryAssetPath(guid, outRelativePath);
}
void AssetImportService::BuildLookupSnapshot(std::unordered_map<std::string, AssetGUID>& outPathToGuid,
std::unordered_map<AssetGUID, Containers::String>& outGuidToPath) const {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
outPathToGuid.clear();
outGuidToPath.clear();
if (m_projectRoot.Empty()) {
return;
}
m_assetDatabase.BuildLookupSnapshot(outPathToGuid, outGuidToPath);
}
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,173 @@
#include <XCEngine/Core/Asset/ProjectAssetIndex.h>
#include <XCEngine/Core/Asset/AssetImportService.h>
#include <algorithm>
#include <cctype>
#include <filesystem>
#include <mutex>
namespace XCEngine {
namespace Resources {
namespace {
std::string ToStdString(const Containers::String& value) {
return std::string(value.CStr());
}
Containers::String NormalizePathString(const std::filesystem::path& path) {
return Containers::String(path.lexically_normal().generic_string().c_str());
}
bool HasVirtualPathScheme(const Containers::String& value) {
return ToStdString(value).find("://") != std::string::npos;
}
Containers::String MakeAssetLookupRelativePath(const Containers::String& projectRoot,
const Containers::String& requestPath) {
if (requestPath.Empty() || HasVirtualPathScheme(requestPath)) {
return Containers::String();
}
const std::filesystem::path inputPath(requestPath.CStr());
if (inputPath.is_absolute()) {
if (projectRoot.Empty()) {
return Containers::String();
}
std::error_code ec;
const std::filesystem::path relativePath =
std::filesystem::relative(inputPath, std::filesystem::path(projectRoot.CStr()), ec);
if (ec) {
return Containers::String();
}
const Containers::String normalizedRelative = NormalizePathString(relativePath);
if (normalizedRelative.StartsWith("Assets/") || normalizedRelative == "Assets") {
return normalizedRelative;
}
return Containers::String();
}
const Containers::String normalizedRequest = NormalizePathString(inputPath);
if (normalizedRequest.StartsWith("Assets/") || normalizedRequest == "Assets") {
return normalizedRequest;
}
return Containers::String();
}
std::string MakeAssetLookupPathKey(const Containers::String& relativePath) {
std::string key = ToStdString(relativePath);
std::transform(key.begin(), key.end(), key.begin(), [](unsigned char ch) {
return static_cast<char>(std::tolower(ch));
});
return key;
}
} // namespace
void ProjectAssetIndex::ResetProjectRoot(const Containers::String& projectRoot) {
std::unique_lock<std::shared_mutex> lock(m_mutex);
m_projectRoot = projectRoot;
m_assetGuidByPathKey.clear();
m_assetPathByGuid.clear();
}
void ProjectAssetIndex::RefreshFrom(const AssetImportService& importService) {
std::unordered_map<std::string, AssetGUID> pathToGuid;
std::unordered_map<AssetGUID, Containers::String> guidToPath;
const Containers::String projectRoot = importService.GetProjectRoot();
if (!projectRoot.Empty()) {
importService.BuildLookupSnapshot(pathToGuid, guidToPath);
}
std::unique_lock<std::shared_mutex> lock(m_mutex);
m_projectRoot = projectRoot;
m_assetGuidByPathKey = std::move(pathToGuid);
m_assetPathByGuid = std::move(guidToPath);
}
bool ProjectAssetIndex::TryGetAssetRef(AssetImportService& importService,
const Containers::String& path,
ResourceType resourceType,
AssetRef& outRef) const {
bool resolved = false;
{
std::shared_lock<std::shared_mutex> lock(m_mutex);
const Containers::String relativePath = MakeAssetLookupRelativePath(m_projectRoot, path);
if (!relativePath.Empty()) {
const auto lookupIt = m_assetGuidByPathKey.find(MakeAssetLookupPathKey(relativePath));
if (lookupIt != m_assetGuidByPathKey.end()) {
outRef.assetGuid = lookupIt->second;
outRef.localID = kMainAssetLocalID;
outRef.resourceType = resourceType;
resolved = outRef.IsValid();
}
}
}
if (!resolved) {
resolved = importService.TryGetAssetRef(path, resourceType, outRef);
if (!resolved) {
const Containers::String projectRoot = importService.GetProjectRoot();
const Containers::String relativePath = MakeAssetLookupRelativePath(projectRoot, path);
if (!relativePath.Empty() && !projectRoot.Empty()) {
auto* index = const_cast<ProjectAssetIndex*>(this);
importService.Refresh();
index->RefreshFrom(importService);
resolved = importService.TryGetAssetRef(path, resourceType, outRef);
}
}
if (resolved) {
Containers::String relativePath;
if (importService.TryGetPrimaryAssetPath(outRef.assetGuid, relativePath)) {
const_cast<ProjectAssetIndex*>(this)->RememberResolvedPath(outRef.assetGuid, relativePath);
}
}
}
return resolved;
}
bool ProjectAssetIndex::TryResolveAssetPath(const AssetImportService& importService,
const AssetRef& assetRef,
Containers::String& outPath) const {
if (!assetRef.IsValid()) {
return false;
}
bool resolved = false;
{
std::shared_lock<std::shared_mutex> lock(m_mutex);
const auto lookupIt = m_assetPathByGuid.find(assetRef.assetGuid);
if (lookupIt != m_assetPathByGuid.end()) {
outPath = lookupIt->second;
resolved = true;
}
}
if (!resolved) {
resolved = importService.TryGetPrimaryAssetPath(assetRef.assetGuid, outPath);
if (resolved) {
const_cast<ProjectAssetIndex*>(this)->RememberResolvedPath(assetRef.assetGuid, outPath);
}
}
return resolved;
}
void ProjectAssetIndex::RememberResolvedPath(const AssetGUID& assetGuid, const Containers::String& relativePath) {
if (!assetGuid.IsValid() || relativePath.Empty()) {
return;
}
std::unique_lock<std::shared_mutex> lock(m_mutex);
m_assetGuidByPathKey[MakeAssetLookupPathKey(relativePath)] = assetGuid;
m_assetPathByGuid[assetGuid] = relativePath;
}
} // namespace Resources
} // namespace XCEngine

View File

@@ -85,6 +85,7 @@ void ResourceManager::EnsureInitialized() {
RegisterBuiltinLoader(*this, g_meshLoader);
RegisterBuiltinLoader(*this, g_shaderLoader);
RegisterBuiltinLoader(*this, g_textureLoader);
m_assetImportService.Initialize();
m_asyncLoader = std::move(asyncLoader);
}
@@ -96,23 +97,24 @@ void ResourceManager::Shutdown() {
m_asyncLoader.reset();
}
std::lock_guard<std::recursive_mutex> lock(m_ioMutex);
m_assetDatabase.Shutdown();
m_assetImportService.Shutdown();
ResourceFileSystem::Get().Shutdown();
m_projectAssetIndex.ResetProjectRoot();
std::lock_guard<std::mutex> inFlightLock(m_inFlightLoadsMutex);
m_inFlightLoads.clear();
}
void ResourceManager::SetResourceRoot(const Containers::String& rootPath) {
std::lock_guard<std::recursive_mutex> lock(m_ioMutex);
m_resourceRoot = rootPath;
if (!m_resourceRoot.Empty()) {
ResourceFileSystem::Get().Initialize(rootPath);
m_assetDatabase.Initialize(rootPath);
m_assetImportService.SetProjectRoot(rootPath);
m_projectAssetIndex.RefreshFrom(m_assetImportService);
} else {
m_assetImportService.SetProjectRoot(Containers::String());
ResourceFileSystem::Get().Shutdown();
m_assetDatabase.Shutdown();
m_projectAssetIndex.ResetProjectRoot();
}
}
@@ -360,14 +362,14 @@ void ResourceManager::UnloadGroup(const Containers::Array<ResourceGUID>& guids)
void ResourceManager::RefreshAssetDatabase() {
if (!m_resourceRoot.Empty()) {
std::lock_guard<std::recursive_mutex> lock(m_ioMutex);
m_assetDatabase.Refresh();
m_assetImportService.Refresh();
m_projectAssetIndex.RefreshFrom(m_assetImportService);
}
}
bool ResourceManager::TryGetAssetRef(const Containers::String& path, ResourceType resourceType, AssetRef& outRef) const {
std::lock_guard<std::recursive_mutex> lock(m_ioMutex);
const bool resolved = m_assetDatabase.TryGetAssetRef(path, resourceType, outRef);
const bool resolved = m_projectAssetIndex.TryGetAssetRef(m_assetImportService, path, resourceType, outRef);
if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
@@ -384,12 +386,8 @@ bool ResourceManager::TryGetAssetRef(const Containers::String& path, ResourceTyp
}
bool ResourceManager::TryResolveAssetPath(const AssetRef& assetRef, Containers::String& outPath) const {
if (!assetRef.IsValid()) {
return false;
}
const bool resolved = m_projectAssetIndex.TryResolveAssetPath(m_assetImportService, assetRef, outPath);
std::lock_guard<std::recursive_mutex> lock(m_ioMutex);
const bool resolved = m_assetDatabase.TryGetPrimaryAssetPath(assetRef.assetGuid, outPath);
if (resolved && ShouldTraceResourcePath(outPath)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
@@ -512,30 +510,27 @@ LoadResult ResourceManager::LoadResource(const Containers::String& path,
}
Containers::String loadPath = path;
{
std::lock_guard<std::recursive_mutex> ioLock(m_ioMutex);
AssetDatabase::ResolvedAsset resolvedAsset;
if (!m_resourceRoot.Empty() &&
m_assetDatabase.EnsureArtifact(path, type, resolvedAsset) &&
resolvedAsset.artifactReady) {
loadPath = resolvedAsset.artifactMainPath;
if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[ResourceManager] LoadResource artifact path=") +
path +
" artifact=" +
loadPath);
}
} else if (ShouldTraceResourcePath(path)) {
AssetDatabase::ResolvedAsset resolvedAsset;
if (!m_resourceRoot.Empty() &&
m_assetImportService.EnsureArtifact(path, type, resolvedAsset) &&
resolvedAsset.artifactReady) {
m_projectAssetIndex.RememberResolvedPath(resolvedAsset.assetGuid, resolvedAsset.relativePath);
loadPath = resolvedAsset.artifactMainPath;
if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[ResourceManager] LoadResource direct path=") +
Containers::String("[ResourceManager] LoadResource artifact path=") +
path +
" loadPath=" +
" artifact=" +
loadPath);
}
} else if (ShouldTraceResourcePath(path)) {
Debug::Logger::Get().Info(
Debug::LogCategory::FileSystem,
Containers::String("[ResourceManager] LoadResource direct path=") +
path +
" loadPath=" +
loadPath);
}
LoadResult result;

View File

@@ -16,10 +16,12 @@ void InputManager::Initialize(void* platformWindowHandle) {
m_keyDownThisFrame.resize(256, false);
m_keyDownLastFrame.resize(256, false);
m_keyUpThisFrame.resize(256, false);
m_keyDown.resize(256, false);
m_mouseButtonDownThisFrame.resize(5, false);
m_mouseButtonDownLastFrame.resize(5, false);
m_mouseButtonUpThisFrame.resize(5, false);
m_mouseButtonDown.resize(5, false);
m_buttonDownThisFrame.resize(32, false);
@@ -39,14 +41,21 @@ void InputManager::Initialize(void* platformWindowHandle) {
}
void InputManager::Shutdown() {
m_mousePosition = Math::Vector2::Zero();
m_mouseDelta = Math::Vector2::Zero();
m_mouseScrollDelta = 0.0f;
m_touches.clear();
if (!m_initialized) return;
m_keyDownThisFrame.clear();
m_keyDownLastFrame.clear();
m_keyUpThisFrame.clear();
m_keyDown.clear();
m_mouseButtonDownThisFrame.clear();
m_mouseButtonDownLastFrame.clear();
m_mouseButtonUpThisFrame.clear();
m_mouseButtonDown.clear();
m_axes.clear();
@@ -64,10 +73,14 @@ void InputManager::Update(float deltaTime) {
m_keyDownLastFrame = m_keyDownThisFrame;
m_keyDownThisFrame.clear();
m_keyDownThisFrame.resize(256, false);
m_keyUpThisFrame.clear();
m_keyUpThisFrame.resize(256, false);
m_mouseButtonDownLastFrame = m_mouseButtonDownThisFrame;
m_mouseButtonDownThisFrame.clear();
m_mouseButtonDownThisFrame.resize(5, false);
m_mouseButtonUpThisFrame.clear();
m_mouseButtonUpThisFrame.resize(5, false);
m_buttonDownLastFrame = m_buttonDownThisFrame;
m_buttonDownThisFrame.clear();
@@ -104,6 +117,13 @@ bool InputManager::IsKeyPressed(KeyCode key) const {
return m_keyDownThisFrame[index] && !m_keyDownLastFrame[index];
}
bool InputManager::IsKeyReleased(KeyCode key) const {
if (!m_initialized) return false;
size_t index = GetKeyIndex(key);
if (index >= m_keyUpThisFrame.size()) return false;
return m_keyUpThisFrame[index];
}
Math::Vector2 InputManager::GetMousePosition() const {
return m_mousePosition;
}
@@ -135,6 +155,13 @@ bool InputManager::IsMouseButtonClicked(MouseButton button) const {
return m_mouseButtonDownThisFrame[index] && !m_mouseButtonDownLastFrame[index];
}
bool InputManager::IsMouseButtonReleased(MouseButton button) const {
if (!m_initialized) return false;
size_t index = GetMouseButtonIndex(button);
if (index >= m_mouseButtonUpThisFrame.size()) return false;
return m_mouseButtonUpThisFrame[index];
}
int InputManager::GetTouchCount() const {
return static_cast<int>(m_touches.size());
}
@@ -170,10 +197,10 @@ float InputManager::GetAxisRaw(const Containers::String& axisName) const {
const auto& axis = it->second;
float value = 0.0f;
if (axis.GetPositiveKey() != KeyCode::None && IsKeyPressed(axis.GetPositiveKey())) {
if (axis.GetPositiveKey() != KeyCode::None && IsKeyDown(axis.GetPositiveKey())) {
value += 1.0f;
}
if (axis.GetNegativeKey() != KeyCode::None && IsKeyPressed(axis.GetNegativeKey())) {
if (axis.GetNegativeKey() != KeyCode::None && IsKeyDown(axis.GetNegativeKey())) {
value -= 1.0f;
}
@@ -194,8 +221,34 @@ bool InputManager::GetButtonDown(const Containers::String& buttonName) const {
bool InputManager::GetButtonUp(const Containers::String& buttonName) const {
auto it = m_buttons.find(buttonName);
if (it == m_buttons.end()) return true;
return IsKeyUp(it->second);
if (it == m_buttons.end()) return false;
return IsKeyReleased(it->second);
}
bool InputManager::IsAnyKeyDown() const {
if (!m_initialized) return false;
return std::any_of(
m_keyDown.begin(),
m_keyDown.end(),
[](bool isDown) { return isDown; })
|| std::any_of(
m_mouseButtonDown.begin(),
m_mouseButtonDown.end(),
[](bool isDown) { return isDown; });
}
bool InputManager::IsAnyKeyPressed() const {
if (!m_initialized) return false;
return std::any_of(
m_keyDownThisFrame.begin(),
m_keyDownThisFrame.end(),
[](bool isPressed) { return isPressed; })
|| std::any_of(
m_mouseButtonDownThisFrame.begin(),
m_mouseButtonDownThisFrame.end(),
[](bool isPressed) { return isPressed; });
}
void InputManager::RegisterAxis(const InputAxis& axis) {
@@ -238,6 +291,7 @@ void InputManager::ProcessKeyUp(KeyCode key, bool alt, bool ctrl, bool shift, bo
if (index >= m_keyDown.size()) return;
m_keyDown[index] = false;
m_keyUpThisFrame[index] = true;
KeyEvent event;
event.keyCode = key;
@@ -274,6 +328,8 @@ void InputManager::ProcessMouseButton(MouseButton button, bool pressed, int x, i
m_mouseButtonDown[index] = pressed;
if (pressed) {
m_mouseButtonDownThisFrame[index] = true;
} else {
m_mouseButtonUpThisFrame[index] = true;
}
MouseButtonEvent event;

View File

@@ -231,6 +231,25 @@ bool CameraRenderer::Render(
}
ShutdownPassSequence(&builtinPostProcessPasses, builtinPostProcessPassesInitialized);
bool overlayPassesInitialized = false;
if (!InitializePassSequence(
request.overlayPasses,
request.context,
overlayPassesInitialized)) {
ShutdownPassSequence(request.postScenePasses, postScenePassesInitialized);
ShutdownPassSequence(request.preScenePasses, preScenePassesInitialized);
return false;
}
if (request.overlayPasses != nullptr &&
!request.overlayPasses->Execute(passContext)) {
ShutdownPassSequence(request.overlayPasses, overlayPassesInitialized);
ShutdownPassSequence(request.postScenePasses, postScenePassesInitialized);
ShutdownPassSequence(request.preScenePasses, preScenePassesInitialized);
return false;
}
ShutdownPassSequence(request.overlayPasses, overlayPassesInitialized);
ShutdownPassSequence(request.postScenePasses, postScenePassesInitialized);
ShutdownPassSequence(request.preScenePasses, preScenePassesInitialized);

View File

@@ -9,6 +9,7 @@
#include <cstring>
#include <cstdlib>
#include <filesystem>
#include <functional>
#include <fstream>
#include <string>
@@ -64,6 +65,56 @@ Containers::String NormalizePathString(const std::filesystem::path& path) {
return Containers::String(path.lexically_normal().generic_string().c_str());
}
bool IsProjectRelativePath(const std::filesystem::path& path) {
const std::string generic = path.generic_string();
return !generic.empty() &&
generic != "." &&
generic != ".." &&
generic.rfind("../", 0) != 0;
}
Containers::String ToProjectRelativeIfPossible(const std::filesystem::path& path) {
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
const std::filesystem::path normalizedPath = path.lexically_normal();
if (!resourceRoot.Empty() && normalizedPath.is_absolute()) {
std::error_code ec;
const std::filesystem::path relativePath =
std::filesystem::relative(normalizedPath, std::filesystem::path(resourceRoot.CStr()), ec);
if (!ec && IsProjectRelativePath(relativePath)) {
return NormalizePathString(relativePath);
}
}
return NormalizePathString(normalizedPath);
}
Containers::String ResolveSourceDependencyPath(const Containers::String& dependencyPath,
const Containers::String& sourcePath) {
if (dependencyPath.Empty()) {
return dependencyPath;
}
std::filesystem::path dependencyFsPath(dependencyPath.CStr());
if (dependencyFsPath.is_absolute()) {
return NormalizePathString(dependencyFsPath);
}
const std::filesystem::path sourceFsPath(sourcePath.CStr());
if (sourceFsPath.is_absolute()) {
return ToProjectRelativeIfPossible(sourceFsPath.parent_path() / dependencyFsPath);
}
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
if (!resourceRoot.Empty()) {
return ToProjectRelativeIfPossible(
std::filesystem::path(resourceRoot.CStr()) /
sourceFsPath.parent_path() /
dependencyFsPath);
}
return NormalizePathString(sourceFsPath.parent_path() / dependencyFsPath);
}
Containers::String ResolveArtifactDependencyPath(const Containers::String& dependencyPath,
const Containers::String& ownerArtifactPath) {
if (dependencyPath.Empty()) {
@@ -358,6 +409,125 @@ bool TryParseTagMap(const std::string& objectText, Material* material) {
return false;
}
bool TryParseStringMapObject(
const std::string& objectText,
const std::function<void(const Containers::String&, const Containers::String&)>& onEntry) {
if (!onEntry || objectText.empty() || objectText.front() != '{' || objectText.back() != '}') {
return false;
}
size_t pos = 1;
while (pos < objectText.size()) {
pos = SkipWhitespace(objectText, pos);
if (pos >= objectText.size()) {
return false;
}
if (objectText[pos] == '}') {
return true;
}
Containers::String key;
if (!ParseQuotedString(objectText, pos, key, &pos)) {
return false;
}
pos = SkipWhitespace(objectText, pos);
if (pos >= objectText.size() || objectText[pos] != ':') {
return false;
}
pos = SkipWhitespace(objectText, pos + 1);
Containers::String value;
if (!ParseQuotedString(objectText, pos, value, &pos)) {
return false;
}
onEntry(key, value);
pos = SkipWhitespace(objectText, pos);
if (pos >= objectText.size()) {
return false;
}
if (objectText[pos] == ',') {
++pos;
continue;
}
if (objectText[pos] == '}') {
return true;
}
return false;
}
return false;
}
bool TryApplyTexturePath(Material* material,
const Containers::String& textureName,
const Containers::String& texturePath) {
if (material == nullptr || textureName.Empty() || texturePath.Empty()) {
return false;
}
material->SetTexturePath(
textureName,
ResolveSourceDependencyPath(texturePath, material->GetPath()));
return true;
}
bool TryParseMaterialTextureBindings(const std::string& jsonText, Material* material) {
if (material == nullptr) {
return false;
}
static const char* const kKnownTextureKeys[] = {
"baseColorTexture",
"_BaseColorTexture",
"_MainTex",
"normalTexture",
"_BumpMap",
"specularTexture",
"emissiveTexture",
"metallicTexture",
"roughnessTexture",
"occlusionTexture",
"opacityTexture"
};
for (const char* key : kKnownTextureKeys) {
if (!HasKey(jsonText, key)) {
continue;
}
Containers::String texturePath;
if (!TryParseStringValue(jsonText, key, texturePath)) {
return false;
}
TryApplyTexturePath(material, Containers::String(key), texturePath);
}
if (HasKey(jsonText, "textures")) {
std::string texturesObject;
if (!TryExtractObject(jsonText, "textures", texturesObject)) {
return false;
}
if (!TryParseStringMapObject(
texturesObject,
[material](const Containers::String& name, const Containers::String& value) {
TryApplyTexturePath(material, name, value);
})) {
return false;
}
}
return true;
}
bool TryParseCullMode(const Containers::String& value, MaterialCullMode& outMode) {
const Containers::String normalized = value.Trim().ToLower();
if (normalized == "none" || normalized == "off") {
@@ -953,6 +1123,10 @@ bool MaterialLoader::ParseMaterialData(const Containers::Array<Core::uint8>& dat
}
}
if (!TryParseMaterialTextureBindings(jsonText, material)) {
return false;
}
return true;
}

View File

@@ -7,6 +7,7 @@
#include "Components/MeshRendererComponent.h"
#include "Components/TransformComponent.h"
#include "Debug/Logger.h"
#include "Input/InputManager.h"
#include "Scene/Scene.h"
#include "Scripting/ScriptComponent.h"
#include "Scripting/ScriptEngine.h"
@@ -356,6 +357,92 @@ float InternalCall_Time_GetDeltaTime() {
return GetInternalCallDeltaTime();
}
float InternalCall_Time_GetFixedDeltaTime() {
return ScriptEngine::Get().GetRuntimeFixedDeltaTime();
}
mono_bool InternalCall_Input_GetKey(int32_t keyCode) {
return XCEngine::Input::InputManager::Get().IsKeyDown(
static_cast<XCEngine::Input::KeyCode>(keyCode)) ? 1 : 0;
}
mono_bool InternalCall_Input_GetKeyDown(int32_t keyCode) {
return XCEngine::Input::InputManager::Get().IsKeyPressed(
static_cast<XCEngine::Input::KeyCode>(keyCode)) ? 1 : 0;
}
mono_bool InternalCall_Input_GetKeyUp(int32_t keyCode) {
return XCEngine::Input::InputManager::Get().IsKeyReleased(
static_cast<XCEngine::Input::KeyCode>(keyCode)) ? 1 : 0;
}
mono_bool InternalCall_Input_GetMouseButton(int32_t button) {
return XCEngine::Input::InputManager::Get().IsMouseButtonDown(
static_cast<XCEngine::Input::MouseButton>(button)) ? 1 : 0;
}
mono_bool InternalCall_Input_GetMouseButtonDown(int32_t button) {
return XCEngine::Input::InputManager::Get().IsMouseButtonClicked(
static_cast<XCEngine::Input::MouseButton>(button)) ? 1 : 0;
}
mono_bool InternalCall_Input_GetMouseButtonUp(int32_t button) {
return XCEngine::Input::InputManager::Get().IsMouseButtonReleased(
static_cast<XCEngine::Input::MouseButton>(button)) ? 1 : 0;
}
mono_bool InternalCall_Input_GetButton(MonoString* buttonName) {
return XCEngine::Input::InputManager::Get().GetButton(
XCEngine::Containers::String(MonoStringToUtf8(buttonName).c_str())) ? 1 : 0;
}
mono_bool InternalCall_Input_GetButtonDown(MonoString* buttonName) {
return XCEngine::Input::InputManager::Get().GetButtonDown(
XCEngine::Containers::String(MonoStringToUtf8(buttonName).c_str())) ? 1 : 0;
}
mono_bool InternalCall_Input_GetButtonUp(MonoString* buttonName) {
return XCEngine::Input::InputManager::Get().GetButtonUp(
XCEngine::Containers::String(MonoStringToUtf8(buttonName).c_str())) ? 1 : 0;
}
float InternalCall_Input_GetAxis(MonoString* axisName) {
return XCEngine::Input::InputManager::Get().GetAxis(
XCEngine::Containers::String(MonoStringToUtf8(axisName).c_str()));
}
float InternalCall_Input_GetAxisRaw(MonoString* axisName) {
return XCEngine::Input::InputManager::Get().GetAxisRaw(
XCEngine::Containers::String(MonoStringToUtf8(axisName).c_str()));
}
mono_bool InternalCall_Input_GetAnyKey() {
return XCEngine::Input::InputManager::Get().IsAnyKeyDown() ? 1 : 0;
}
mono_bool InternalCall_Input_GetAnyKeyDown() {
return XCEngine::Input::InputManager::Get().IsAnyKeyPressed() ? 1 : 0;
}
void InternalCall_Input_GetMousePosition(XCEngine::Math::Vector3* outPosition) {
if (!outPosition) {
return;
}
const XCEngine::Math::Vector2 position = XCEngine::Input::InputManager::Get().GetMousePosition();
*outPosition = XCEngine::Math::Vector3(position.x, position.y, 0.0f);
}
void InternalCall_Input_GetMouseScrollDelta(XCEngine::Math::Vector2* outDelta) {
if (!outDelta) {
return;
}
*outDelta = XCEngine::Math::Vector2(
0.0f,
XCEngine::Input::InputManager::Get().GetMouseScrollDelta());
}
MonoString* InternalCall_GameObject_GetName(uint64_t gameObjectUUID) {
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
return mono_string_new(
@@ -1131,6 +1218,22 @@ void RegisterInternalCalls() {
mono_add_internal_call("XCEngine.InternalCalls::Debug_LogWarning", reinterpret_cast<const void*>(&InternalCall_Debug_LogWarning));
mono_add_internal_call("XCEngine.InternalCalls::Debug_LogError", reinterpret_cast<const void*>(&InternalCall_Debug_LogError));
mono_add_internal_call("XCEngine.InternalCalls::Time_GetDeltaTime", reinterpret_cast<const void*>(&InternalCall_Time_GetDeltaTime));
mono_add_internal_call("XCEngine.InternalCalls::Time_GetFixedDeltaTime", reinterpret_cast<const void*>(&InternalCall_Time_GetFixedDeltaTime));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetKey", reinterpret_cast<const void*>(&InternalCall_Input_GetKey));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetKeyDown", reinterpret_cast<const void*>(&InternalCall_Input_GetKeyDown));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetKeyUp", reinterpret_cast<const void*>(&InternalCall_Input_GetKeyUp));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetMouseButton", reinterpret_cast<const void*>(&InternalCall_Input_GetMouseButton));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetMouseButtonDown", reinterpret_cast<const void*>(&InternalCall_Input_GetMouseButtonDown));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetMouseButtonUp", reinterpret_cast<const void*>(&InternalCall_Input_GetMouseButtonUp));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetButton", reinterpret_cast<const void*>(&InternalCall_Input_GetButton));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetButtonDown", reinterpret_cast<const void*>(&InternalCall_Input_GetButtonDown));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetButtonUp", reinterpret_cast<const void*>(&InternalCall_Input_GetButtonUp));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetAxis", reinterpret_cast<const void*>(&InternalCall_Input_GetAxis));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetAxisRaw", reinterpret_cast<const void*>(&InternalCall_Input_GetAxisRaw));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetAnyKey", reinterpret_cast<const void*>(&InternalCall_Input_GetAnyKey));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetAnyKeyDown", reinterpret_cast<const void*>(&InternalCall_Input_GetAnyKeyDown));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetMousePosition", reinterpret_cast<const void*>(&InternalCall_Input_GetMousePosition));
mono_add_internal_call("XCEngine.InternalCalls::Input_GetMouseScrollDelta", reinterpret_cast<const void*>(&InternalCall_Input_GetMouseScrollDelta));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetName", reinterpret_cast<const void*>(&InternalCall_GameObject_GetName));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_SetName", reinterpret_cast<const void*>(&InternalCall_GameObject_SetName));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetActiveSelf", reinterpret_cast<const void*>(&InternalCall_GameObject_GetActiveSelf));
@@ -1281,20 +1384,57 @@ bool MonoScriptRuntime::IsClassAvailable(
return FindClassMetadata(assemblyName, namespaceName, className) != nullptr;
}
std::vector<std::string> MonoScriptRuntime::GetScriptClassNames(const std::string& assemblyName) const {
std::vector<std::string> classNames;
classNames.reserve(m_classes.size());
bool MonoScriptRuntime::TryGetAvailableScriptClasses(
std::vector<ScriptClassDescriptor>& outClasses) const {
outClasses.clear();
if (!m_initialized) {
return false;
}
outClasses.reserve(m_classes.size());
for (const auto& [key, metadata] : m_classes) {
(void)key;
if (!assemblyName.empty() && metadata.assemblyName != assemblyName) {
outClasses.push_back(
ScriptClassDescriptor{
metadata.assemblyName,
metadata.namespaceName,
metadata.className
});
}
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;
}
std::vector<std::string> MonoScriptRuntime::GetScriptClassNames(const std::string& assemblyName) const {
std::vector<ScriptClassDescriptor> classes;
if (!TryGetAvailableScriptClasses(classes)) {
return {};
}
std::vector<std::string> classNames;
classNames.reserve(classes.size());
for (const ScriptClassDescriptor& descriptor : classes) {
if (!assemblyName.empty() && descriptor.assemblyName != assemblyName) {
continue;
}
classNames.push_back(metadata.fullName);
classNames.push_back(descriptor.GetFullName());
}
std::sort(classNames.begin(), classNames.end());
return classNames;
}

View File

@@ -11,6 +11,12 @@ void NullScriptRuntime::OnRuntimeStop(Components::Scene* scene) {
(void)scene;
}
bool NullScriptRuntime::TryGetAvailableScriptClasses(
std::vector<ScriptClassDescriptor>& outClasses) const {
outClasses.clear();
return false;
}
bool NullScriptRuntime::TryGetClassFieldMetadata(
const std::string& assemblyName,
const std::string& namespaceName,

View File

@@ -25,23 +25,36 @@ ScriptComponent::ScriptComponent()
void ScriptComponent::SetScriptClass(const std::string& namespaceName, const std::string& className) {
const bool hadScriptClass = HasScriptClass();
const bool changed = m_namespaceName != namespaceName || m_className != className;
m_namespaceName = namespaceName;
m_className = className;
if (!hadScriptClass && HasScriptClass()) {
ScriptEngine::Get().OnScriptComponentEnabled(this);
} else if (hadScriptClass && changed) {
ScriptEngine::Get().OnScriptComponentClassChanged(this);
}
}
void ScriptComponent::SetScriptClass(const std::string& assemblyName, const std::string& namespaceName, const std::string& className) {
const bool hadScriptClass = HasScriptClass();
const bool changed =
m_assemblyName != assemblyName ||
m_namespaceName != namespaceName ||
m_className != className;
m_assemblyName = assemblyName;
m_namespaceName = namespaceName;
m_className = className;
if (!hadScriptClass && HasScriptClass()) {
ScriptEngine::Get().OnScriptComponentEnabled(this);
} else if (hadScriptClass && changed) {
ScriptEngine::Get().OnScriptComponentClassChanged(this);
}
}
void ScriptComponent::ClearScriptClass() {
SetScriptClass(m_assemblyName, std::string(), std::string());
}
std::string ScriptComponent::GetFullClassName() const {
if (m_className.empty()) {
return std::string();

View File

@@ -63,8 +63,19 @@ 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;
OnRuntimeStop();
m_runtimeFixedDeltaTime = configuredFixedDeltaTime;
if (!scene) {
return;
@@ -109,6 +120,7 @@ void ScriptEngine::OnRuntimeStop() {
m_runtimeScene = nullptr;
m_scriptStates.clear();
m_scriptOrder.clear();
m_runtimeFixedDeltaTime = DefaultFixedDeltaTime;
return;
}
@@ -125,6 +137,7 @@ void ScriptEngine::OnRuntimeStop() {
m_scriptOrder.clear();
m_runtimeRunning = false;
m_runtimeScene = nullptr;
m_runtimeFixedDeltaTime = DefaultFixedDeltaTime;
m_runtime->OnRuntimeStop(stoppedScene);
}
@@ -239,6 +252,33 @@ void ScriptEngine::OnScriptComponentDestroyed(ScriptComponent* component) {
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;
}
@@ -248,6 +288,45 @@ bool ScriptEngine::HasRuntimeInstance(const ScriptComponent* component) const {
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,