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

@@ -246,6 +246,8 @@ add_library(XCEngine STATIC
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/AssetRef.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ArtifactFormats.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/AssetDatabase.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/AssetImportService.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ProjectAssetIndex.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ResourceTypes.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ImportSettings.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ResourceHandle.h
@@ -255,6 +257,8 @@ add_library(XCEngine STATIC
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ResourceDependencyGraph.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/AssetGUID.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/AssetDatabase.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/AssetImportService.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/ProjectAssetIndex.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/ResourceManager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/ResourceCache.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/AsyncLoader.cpp

View File

@@ -3,6 +3,7 @@
#include <XCEngine/Components/Component.h>
#include <XCEngine/Core/Asset/AssetRef.h>
#include <XCEngine/Core/Asset/ResourceHandle.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <memory>
@@ -32,12 +33,14 @@ private:
struct PendingMeshLoadState;
void BeginAsyncMeshLoad(const std::string& meshPath);
void EnsureDeferredAsyncMeshLoadStarted();
void ResolvePendingMesh();
Resources::ResourceHandle<Resources::Mesh> m_mesh;
std::string m_meshPath;
Resources::AssetRef m_meshRef;
std::shared_ptr<PendingMeshLoadState> m_pendingMeshLoad;
bool m_asyncMeshLoadRequested = false;
};
} // namespace Components

View File

@@ -46,6 +46,7 @@ private:
struct PendingMaterialLoadState;
void BeginAsyncMaterialLoad(size_t index, const std::string& materialPath);
void EnsureDeferredAsyncMaterialLoadStarted(size_t index);
void ResolvePendingMaterials();
void EnsureMaterialSlot(size_t index);
static std::string MaterialPathFromHandle(const Resources::ResourceHandle<Resources::Material>& material);
@@ -54,6 +55,7 @@ private:
std::vector<std::string> m_materialPaths;
std::vector<Resources::AssetRef> m_materialRefs;
std::vector<std::shared_ptr<PendingMaterialLoadState>> m_pendingMaterialLoads;
std::vector<bool> m_asyncMaterialLoadRequested;
bool m_castShadows = true;
bool m_receiveShadows = true;
uint32_t m_renderLayer = 0;

View File

@@ -6,12 +6,23 @@
#include <filesystem>
#include <unordered_map>
#include <vector>
namespace XCEngine {
namespace Resources {
class Mesh;
class Material;
class AssetDatabase {
public:
struct ArtifactDependencyRecord {
Containers::String path;
Containers::String hash;
Core::uint64 fileSize = 0;
Core::uint64 writeTime = 0;
};
struct SourceAssetRecord {
AssetGUID guid;
Containers::String relativePath;
@@ -39,6 +50,7 @@ public:
Core::uint64 sourceFileSize = 0;
Core::uint64 sourceWriteTime = 0;
LocalID mainLocalID = kMainAssetLocalID;
std::vector<ArtifactDependencyRecord> dependencies;
};
struct ResolvedAsset {
@@ -66,13 +78,15 @@ public:
ResourceType requestedType,
ResolvedAsset& outAsset);
bool TryGetPrimaryAssetPath(const AssetGUID& guid, Containers::String& outRelativePath) const;
void BuildLookupSnapshot(std::unordered_map<std::string, AssetGUID>& outPathToGuid,
std::unordered_map<AssetGUID, Containers::String>& outGuidToPath) const;
const Containers::String& GetProjectRoot() const { return m_projectRoot; }
const Containers::String& GetAssetsRoot() const { return m_assetsRoot; }
const Containers::String& GetLibraryRoot() const { return m_libraryRoot; }
private:
static constexpr Core::uint32 kCurrentImporterVersion = 2;
static constexpr Core::uint32 kCurrentImporterVersion = 3;
void EnsureProjectLayout();
void LoadSourceAssetDB();
@@ -110,12 +124,24 @@ private:
bool ImportModelAsset(const SourceAssetRecord& sourceRecord,
ArtifactRecord& outRecord);
Containers::String BuildArtifactKey(const SourceAssetRecord& sourceRecord) const;
Containers::String BuildArtifactKey(
const SourceAssetRecord& sourceRecord,
const std::vector<ArtifactDependencyRecord>& dependencies = {}) const;
Containers::String BuildArtifactDirectory(const Containers::String& artifactKey) const;
static Containers::String ReadWholeFileText(const std::filesystem::path& path);
static Containers::String ComputeFileHash(const std::filesystem::path& path);
static Core::uint64 GetFileSizeValue(const std::filesystem::path& path);
static Core::uint64 GetFileWriteTimeValue(const std::filesystem::path& path);
Containers::String NormalizeDependencyPath(const std::filesystem::path& path) const;
std::filesystem::path ResolveDependencyPath(const Containers::String& path) const;
bool CaptureDependencyRecord(const std::filesystem::path& path,
ArtifactDependencyRecord& outRecord) const;
bool AreDependenciesCurrent(const std::vector<ArtifactDependencyRecord>& dependencies) const;
bool CollectModelDependencies(const SourceAssetRecord& sourceRecord,
const Mesh& mesh,
std::vector<ArtifactDependencyRecord>& outDependencies) const;
bool CollectMaterialDependencies(const Material& material,
std::vector<ArtifactDependencyRecord>& outDependencies) const;
Containers::String m_projectRoot;
Containers::String m_assetsRoot;

View File

@@ -0,0 +1,38 @@
#pragma once
#include "AssetDatabase.h"
#include <mutex>
#include <unordered_map>
namespace XCEngine {
namespace Resources {
class AssetImportService {
public:
void Initialize();
void Shutdown();
void SetProjectRoot(const Containers::String& projectRoot);
Containers::String GetProjectRoot() const;
void Refresh();
bool EnsureArtifact(const Containers::String& requestPath,
ResourceType requestedType,
AssetDatabase::ResolvedAsset& outAsset);
bool TryGetAssetRef(const Containers::String& requestPath,
ResourceType resourceType,
AssetRef& outRef) const;
bool TryGetPrimaryAssetPath(const AssetGUID& guid, Containers::String& outRelativePath) const;
void BuildLookupSnapshot(std::unordered_map<std::string, AssetGUID>& outPathToGuid,
std::unordered_map<AssetGUID, Containers::String>& outGuidToPath) const;
private:
mutable std::recursive_mutex m_mutex;
Containers::String m_projectRoot;
AssetDatabase m_assetDatabase;
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -0,0 +1,37 @@
#pragma once
#include <XCEngine/Core/Asset/AssetRef.h>
#include <XCEngine/Core/Asset/ResourceTypes.h>
#include <XCEngine/Core/Containers/String.h>
#include <shared_mutex>
#include <unordered_map>
namespace XCEngine {
namespace Resources {
class AssetImportService;
class ProjectAssetIndex {
public:
void ResetProjectRoot(const Containers::String& projectRoot = Containers::String());
void RefreshFrom(const AssetImportService& importService);
bool TryGetAssetRef(AssetImportService& importService,
const Containers::String& path,
ResourceType resourceType,
AssetRef& outRef) const;
bool TryResolveAssetPath(const AssetImportService& importService,
const AssetRef& assetRef,
Containers::String& outPath) const;
void RememberResolvedPath(const AssetGUID& assetGuid, const Containers::String& relativePath);
private:
mutable std::shared_mutex m_mutex;
Containers::String m_projectRoot;
std::unordered_map<std::string, AssetGUID> m_assetGuidByPathKey;
std::unordered_map<AssetGUID, Containers::String> m_assetPathByGuid;
};
} // namespace Resources
} // namespace XCEngine

View File

@@ -1,7 +1,8 @@
#pragma once
#include <XCEngine/Core/IO/IResourceLoader.h>
#include "AssetDatabase.h"
#include "AssetImportService.h"
#include "ProjectAssetIndex.h"
#include "ResourceCache.h"
#include "AsyncLoader.h"
#include "ResourceHandle.h"
@@ -165,12 +166,12 @@ private:
size_t m_memoryUsage = 0;
size_t m_memoryBudget = 512 * 1024 * 1024;
AssetDatabase m_assetDatabase;
mutable AssetImportService m_assetImportService;
mutable ProjectAssetIndex m_projectAssetIndex;
ResourceCache m_cache;
Core::UniqueRef<AsyncLoader> m_asyncLoader;
Threading::Mutex m_mutex;
std::mutex m_initializeMutex;
mutable std::recursive_mutex m_ioMutex;
std::mutex m_inFlightLoadsMutex;
std::unordered_map<InFlightLoadKey, std::shared_ptr<InFlightLoadState>, InFlightLoadKeyHasher> m_inFlightLoads;
std::atomic<Core::uint32> m_deferredSceneLoadDepth{0};

View File

@@ -21,6 +21,7 @@ public:
bool IsKeyDown(KeyCode key) const;
bool IsKeyUp(KeyCode key) const;
bool IsKeyPressed(KeyCode key) const;
bool IsKeyReleased(KeyCode key) const;
Math::Vector2 GetMousePosition() const;
Math::Vector2 GetMouseDelta() const;
@@ -28,6 +29,7 @@ public:
bool IsMouseButtonDown(MouseButton button) const;
bool IsMouseButtonUp(MouseButton button) const;
bool IsMouseButtonClicked(MouseButton button) const;
bool IsMouseButtonReleased(MouseButton button) const;
int GetTouchCount() const;
TouchState GetTouch(int index) const;
@@ -38,6 +40,8 @@ public:
bool GetButton(const Containers::String& buttonName) const;
bool GetButtonDown(const Containers::String& buttonName) const;
bool GetButtonUp(const Containers::String& buttonName) const;
bool IsAnyKeyDown() const;
bool IsAnyKeyPressed() const;
void RegisterAxis(const InputAxis& axis);
void RegisterButton(const Containers::String& name, KeyCode key);
@@ -68,6 +72,7 @@ private:
std::vector<bool> m_keyDownThisFrame;
std::vector<bool> m_keyDownLastFrame;
std::vector<bool> m_keyUpThisFrame;
std::vector<bool> m_keyDown;
Math::Vector2 m_mousePosition;
@@ -75,6 +80,7 @@ private:
float m_mouseScrollDelta = 0.0f;
std::vector<bool> m_mouseButtonDownThisFrame;
std::vector<bool> m_mouseButtonDownLastFrame;
std::vector<bool> m_mouseButtonUpThisFrame;
std::vector<bool> m_mouseButtonDown;
std::vector<TouchState> m_touches;

View File

@@ -57,6 +57,7 @@ struct CameraRenderRequest {
Math::Color clearColorOverride = Math::Color::Black();
RenderPassSequence* preScenePasses = nullptr;
RenderPassSequence* postScenePasses = nullptr;
RenderPassSequence* overlayPasses = nullptr;
bool IsValid() const {
return scene != nullptr &&

View File

@@ -28,6 +28,26 @@ enum class ScriptLifecycleMethod {
OnDestroy
};
struct ScriptClassDescriptor {
std::string assemblyName;
std::string namespaceName;
std::string className;
std::string GetFullName() const {
return namespaceName.empty() ? className : namespaceName + "." + className;
}
bool operator==(const ScriptClassDescriptor& other) const {
return assemblyName == other.assemblyName
&& namespaceName == other.namespaceName
&& className == other.className;
}
bool operator!=(const ScriptClassDescriptor& other) const {
return !(*this == other);
}
};
struct ScriptRuntimeContext {
Components::Scene* scene = nullptr;
Components::GameObject* gameObject = nullptr;
@@ -43,6 +63,9 @@ public:
virtual void OnRuntimeStart(Components::Scene* scene) = 0;
virtual void OnRuntimeStop(Components::Scene* scene) = 0;
virtual bool TryGetAvailableScriptClasses(
std::vector<ScriptClassDescriptor>& outClasses) const = 0;
virtual bool TryGetClassFieldMetadata(
const std::string& assemblyName,
const std::string& namespaceName,

View File

@@ -50,6 +50,8 @@ public:
const std::string& namespaceName,
const std::string& className) const;
std::vector<std::string> GetScriptClassNames(const std::string& assemblyName = std::string()) const;
bool TryGetAvailableScriptClasses(
std::vector<ScriptClassDescriptor>& outClasses) const override;
bool TryGetClassFieldMetadata(
const std::string& assemblyName,
const std::string& namespaceName,

View File

@@ -9,6 +9,8 @@ class NullScriptRuntime : public IScriptRuntime {
public:
void OnRuntimeStart(Components::Scene* scene) override;
void OnRuntimeStop(Components::Scene* scene) override;
bool TryGetAvailableScriptClasses(
std::vector<ScriptClassDescriptor>& outClasses) const override;
bool TryGetClassFieldMetadata(
const std::string& assemblyName,
const std::string& namespaceName,

View File

@@ -28,6 +28,7 @@ public:
void SetScriptClass(const std::string& namespaceName, const std::string& className);
void SetScriptClass(const std::string& assemblyName, const std::string& namespaceName, const std::string& className);
void ClearScriptClass();
bool HasScriptClass() const { return !m_className.empty(); }
std::string GetFullClassName() const;

View File

@@ -19,9 +19,12 @@ class ScriptComponent;
class ScriptEngine {
public:
static ScriptEngine& Get();
static constexpr float DefaultFixedDeltaTime = 1.0f / 50.0f;
void SetRuntime(IScriptRuntime* runtime);
IScriptRuntime* GetRuntime() const { return m_runtime; }
void SetRuntimeFixedDeltaTime(float fixedDeltaTime);
float GetRuntimeFixedDeltaTime() const { return m_runtimeFixedDeltaTime; }
void OnRuntimeStart(Components::Scene* scene);
void OnRuntimeStop();
@@ -33,6 +36,7 @@ public:
void OnScriptComponentEnabled(ScriptComponent* component);
void OnScriptComponentDisabled(ScriptComponent* component);
void OnScriptComponentDestroyed(ScriptComponent* component);
void OnScriptComponentClassChanged(ScriptComponent* component);
bool IsRuntimeRunning() const { return m_runtimeRunning; }
Components::Scene* GetRuntimeScene() const { return m_runtimeScene; }
@@ -40,6 +44,9 @@ public:
bool HasTrackedScriptComponent(const ScriptComponent* component) const;
bool HasRuntimeInstance(const ScriptComponent* component) const;
size_t GetTrackedScriptCount() const { return m_scriptOrder.size(); }
bool TryGetAvailableScriptClasses(
std::vector<ScriptClassDescriptor>& outClasses,
const std::string& assemblyName = std::string()) const;
bool TrySetScriptFieldValue(
ScriptComponent* component,
const std::string& fieldName,
@@ -138,6 +145,7 @@ private:
IScriptRuntime* m_runtime = &m_nullRuntime;
Components::Scene* m_runtimeScene = nullptr;
bool m_runtimeRunning = false;
float m_runtimeFixedDeltaTime = DefaultFixedDeltaTime;
uint64_t m_runtimeSceneCreatedSubscription = 0;
std::unordered_map<ScriptInstanceKey, ScriptInstanceState, ScriptInstanceKeyHasher> m_scriptStates;

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,