feat: expand editor scripting asset and viewport flow
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
38
engine/include/XCEngine/Core/Asset/AssetImportService.h
Normal file
38
engine/include/XCEngine/Core/Asset/AssetImportService.h
Normal 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
|
||||
37
engine/include/XCEngine/Core/Asset/ProjectAssetIndex.h
Normal file
37
engine/include/XCEngine/Core/Asset/ProjectAssetIndex.h
Normal 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
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
88
engine/src/Core/Asset/AssetImportService.cpp
Normal file
88
engine/src/Core/Asset/AssetImportService.cpp
Normal 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
|
||||
173
engine/src/Core/Asset/ProjectAssetIndex.cpp
Normal file
173
engine/src/Core/Asset/ProjectAssetIndex.cpp
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user