Merge remote-tracking branch 'origin/master' into sdl_platform
This commit is contained in:
@@ -229,18 +229,12 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float
|
||||
auto& context = *Context.Get();
|
||||
float eventTimeMin = animPrevPos;
|
||||
float eventTimeMax = animPos;
|
||||
if (loop && context.DeltaTime * speed < 0)
|
||||
|
||||
if (eventTimeMin > eventTimeMax)
|
||||
{
|
||||
// Check if animation looped (for anim events shooting during backwards playback)
|
||||
//const float posNotLooped = startTimePos + oldTimePos;
|
||||
//if (posNotLooped < 0.0f || posNotLooped > length)
|
||||
//const int32 animPosCycle = Math::CeilToInt(animPos / anim->GetDuration());
|
||||
//const int32 animPrevPosCycle = Math::CeilToInt(animPrevPos / anim->GetDuration());
|
||||
//if (animPosCycle != animPrevPosCycle)
|
||||
{
|
||||
Swap(eventTimeMin, eventTimeMax);
|
||||
}
|
||||
Swap(eventTimeMin, eventTimeMax);
|
||||
}
|
||||
|
||||
const float eventTime = (float)(animPos / anim->Data.FramesPerSecond);
|
||||
const float eventDeltaTime = (float)((animPos - animPrevPos) / anim->Data.FramesPerSecond);
|
||||
for (const auto& track : anim->Events)
|
||||
@@ -251,7 +245,13 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float
|
||||
continue;
|
||||
const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f;
|
||||
#define ADD_OUTGOING_EVENT(type) context.Data->OutgoingEvents.Add({ k.Value.Instance, (AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime, AnimGraphInstanceData::OutgoingEvent::type })
|
||||
if (k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration)
|
||||
if ((k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration
|
||||
&& (Math::FloorToInt(animPos) != 0 && Math::CeilToInt(animPrevPos) != Math::CeilToInt(anim->GetDuration()) && Math::FloorToInt(animPrevPos) != 0 && Math::CeilToInt(animPos) != Math::CeilToInt(anim->GetDuration())))
|
||||
// Handle the edge case of an event on 0 or on max animation duration during looping
|
||||
|| (loop && duration == 0.0f && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == anim->GetDuration())
|
||||
|| (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f)
|
||||
|| (loop && Math::FloorToInt(animPrevPos) == 0 && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f)
|
||||
)
|
||||
{
|
||||
int32 stateIndex = -1;
|
||||
if (duration > 1)
|
||||
|
||||
@@ -531,7 +531,7 @@ bool Animation::SaveHeader(const ModelData& modelData, WriteStream& stream, int3
|
||||
// Nested animations
|
||||
stream.WriteInt32(0); // Empty list
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
void Animation::GetReferences(Array<Guid>& assets, Array<String>& files) const
|
||||
|
||||
@@ -395,7 +395,7 @@ bool Model::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
@@ -454,7 +454,7 @@ bool Model::SaveHeader(WriteStream& stream, const ModelData& modelData)
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
bool Model::Save(bool withMeshDataFromGpu, Function<FlaxChunk*(int32)>& getChunk) const
|
||||
|
||||
@@ -329,7 +329,7 @@ bool ModelBase::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
stream.Read(slot.Name, 11);
|
||||
}
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
bool ModelBase::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly)
|
||||
|
||||
@@ -656,7 +656,7 @@ bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return stream.HasError();
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
@@ -686,7 +686,7 @@ bool SkinnedModel::SaveHeader(WriteStream& stream) const
|
||||
const int32 blendShapes = mesh.BlendShapes.Count();
|
||||
stream.Write((uint16)blendShapes);
|
||||
for (const auto& blendShape : mesh.BlendShapes)
|
||||
blendShape.Save(stream);
|
||||
blendShape.SaveHeader(stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "StringView.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
String String::Empty;
|
||||
const String String::Empty;
|
||||
|
||||
String::String(const StringAnsi& str)
|
||||
{
|
||||
|
||||
@@ -548,7 +548,7 @@ public:
|
||||
/// <summary>
|
||||
/// Instance of the empty string.
|
||||
/// </summary>
|
||||
static String Empty;
|
||||
static const String Empty;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -9,7 +9,7 @@ StringView StringBuilder::ToStringView() const
|
||||
return StringView(_data.Get(), _data.Count());
|
||||
}
|
||||
|
||||
StringView StringView::Empty;
|
||||
const StringView StringView::Empty;
|
||||
|
||||
StringView::StringView(const String& str)
|
||||
: StringViewBase<Char>(str.Get(), str.Length())
|
||||
|
||||
@@ -219,7 +219,7 @@ public:
|
||||
/// <summary>
|
||||
/// Instance of the empty string.
|
||||
/// </summary>
|
||||
static StringView Empty;
|
||||
static const StringView Empty;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
PACK_STRUCT(struct DecalMaterialShaderData {
|
||||
Matrix WorldMatrix;
|
||||
Matrix InvWorld;
|
||||
Matrix SVPositionToWorld;
|
||||
Matrix SvPositionToWorld;
|
||||
});
|
||||
|
||||
DrawPass DecalMaterialShader::GetDrawModes() const
|
||||
@@ -47,7 +47,9 @@ void DecalMaterialShader::Bind(BindParameters& params)
|
||||
MaterialParams::Bind(params.ParamsLink, bindMeta);
|
||||
|
||||
// Decals use depth buffer to draw on top of the objects
|
||||
context->BindSR(0, GET_TEXTURE_VIEW_SAFE(params.RenderContext.Buffers->DepthBuffer));
|
||||
GPUTexture* depthBuffer = params.RenderContext.Buffers->DepthBuffer;
|
||||
GPUTextureView* depthBufferView = EnumHasAnyFlags(depthBuffer->Flags(), GPUTextureFlags::ReadOnlyDepthView) ? depthBuffer->ViewReadOnlyDepth() : depthBuffer->View();
|
||||
context->BindSR(0, depthBufferView);
|
||||
|
||||
// Setup material constants
|
||||
{
|
||||
@@ -65,7 +67,7 @@ void DecalMaterialShader::Bind(BindParameters& params)
|
||||
0, 0, 1, 0,
|
||||
-1.0f, 1.0f, 0, 1);
|
||||
const Matrix svPositionToWorld = offsetMatrix * view.IVP;
|
||||
Matrix::Transpose(svPositionToWorld, materialData->SVPositionToWorld);
|
||||
Matrix::Transpose(svPositionToWorld, materialData->SvPositionToWorld);
|
||||
}
|
||||
|
||||
// Bind constants
|
||||
@@ -90,16 +92,20 @@ void DecalMaterialShader::Unload()
|
||||
bool DecalMaterialShader::Load()
|
||||
{
|
||||
GPUPipelineState::Description psDesc0 = GPUPipelineState::Description::DefaultNoDepth;
|
||||
psDesc0.VS = _shader->GetVS("VS_Decal");
|
||||
psDesc0.VS = _shader->GetVS("VS_Decal"); // TODO: move VS_Decal to be shared (eg. in GBuffer.shader)
|
||||
if (psDesc0.VS == nullptr)
|
||||
return true;
|
||||
psDesc0.PS = _shader->GetPS("PS_Decal");
|
||||
psDesc0.CullMode = CullMode::Normal;
|
||||
if (GPUDevice::Instance->Limits.HasReadOnlyDepth)
|
||||
{
|
||||
psDesc0.DepthEnable = true;
|
||||
psDesc0.DepthWriteEnable = false;
|
||||
}
|
||||
|
||||
switch (_info.DecalBlendingMode)
|
||||
{
|
||||
case MaterialDecalBlendingMode::Translucent:
|
||||
{
|
||||
psDesc0.BlendMode.BlendEnable = true;
|
||||
psDesc0.BlendMode.SrcBlend = BlendingMode::Blend::SrcAlpha;
|
||||
psDesc0.BlendMode.DestBlend = BlendingMode::Blend::InvSrcAlpha;
|
||||
@@ -107,9 +113,7 @@ bool DecalMaterialShader::Load()
|
||||
psDesc0.BlendMode.DestBlendAlpha = BlendingMode::Blend::One;
|
||||
psDesc0.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Stain:
|
||||
{
|
||||
psDesc0.BlendMode.BlendEnable = true;
|
||||
psDesc0.BlendMode.SrcBlend = BlendingMode::Blend::DestColor;
|
||||
psDesc0.BlendMode.DestBlend = BlendingMode::Blend::InvSrcAlpha;
|
||||
@@ -117,9 +121,7 @@ bool DecalMaterialShader::Load()
|
||||
psDesc0.BlendMode.DestBlendAlpha = BlendingMode::Blend::One;
|
||||
psDesc0.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Normal:
|
||||
{
|
||||
psDesc0.BlendMode.BlendEnable = true;
|
||||
psDesc0.BlendMode.SrcBlend = BlendingMode::Blend::SrcAlpha;
|
||||
psDesc0.BlendMode.DestBlend = BlendingMode::Blend::InvSrcAlpha;
|
||||
@@ -127,13 +129,10 @@ bool DecalMaterialShader::Load()
|
||||
psDesc0.BlendMode.DestBlendAlpha = BlendingMode::Blend::One;
|
||||
psDesc0.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Emissive:
|
||||
{
|
||||
psDesc0.BlendMode = BlendingMode::Additive;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_cache.Outside = GPUDevice::Instance->CreatePipelineState();
|
||||
if (_cache.Outside->Init(psDesc0))
|
||||
@@ -143,6 +142,7 @@ bool DecalMaterialShader::Load()
|
||||
}
|
||||
|
||||
psDesc0.CullMode = CullMode::Inverted;
|
||||
psDesc0.DepthEnable = false;
|
||||
_cache.Inside = GPUDevice::Instance->CreatePipelineState();
|
||||
if (_cache.Inside->Init(psDesc0))
|
||||
{
|
||||
|
||||
@@ -23,6 +23,8 @@ PACK_STRUCT(struct GUIMaterialShaderData {
|
||||
Float4 ViewInfo;
|
||||
Float4 ScreenSize;
|
||||
Float4 ViewSize;
|
||||
Float3 ViewPadding0;
|
||||
float UnscaledTimeParam;
|
||||
});
|
||||
|
||||
void GUIMaterialShader::Bind(BindParameters& params)
|
||||
@@ -55,7 +57,8 @@ void GUIMaterialShader::Bind(BindParameters& params)
|
||||
materialData->ViewPos = Float3::Zero;
|
||||
materialData->ViewFar = 0.0f;
|
||||
materialData->ViewDir = Float3::Forward;
|
||||
materialData->TimeParam = params.TimeParam;
|
||||
materialData->TimeParam = params.Time;
|
||||
materialData->UnscaledTimeParam = params.UnscaledTime;
|
||||
materialData->ViewInfo = Float4::Zero;
|
||||
auto& viewport = Render2D::GetViewport();
|
||||
materialData->ScreenSize = Float4(viewport.Width, viewport.Height, 1.0f / viewport.Width, 1.0f / viewport.Height);
|
||||
|
||||
@@ -148,7 +148,7 @@ public:
|
||||
const ::DrawCall* DrawCall = nullptr;
|
||||
MaterialParamsLink* ParamsLink = nullptr;
|
||||
void* CustomData = nullptr;
|
||||
float TimeParam;
|
||||
float Time, UnscaledTime;
|
||||
bool Instanced = false;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -36,12 +36,15 @@ GPU_CB_STRUCT(MaterialShaderDataPerView {
|
||||
Float4 TemporalAAJitter;
|
||||
Float3 LargeWorldsChunkIndex;
|
||||
float LargeWorldsChunkSize;
|
||||
Float3 ViewPadding0;
|
||||
float UnscaledTimeParam;
|
||||
});
|
||||
|
||||
IMaterial::BindParameters::BindParameters(::GPUContext* context, const ::RenderContext& renderContext)
|
||||
: GPUContext(context)
|
||||
, RenderContext(renderContext)
|
||||
, TimeParam(Time::Draw.UnscaledTime.GetTotalSeconds())
|
||||
, Time(Time::Draw.Time.GetTotalSeconds())
|
||||
, UnscaledTime(Time::Draw.UnscaledTime.GetTotalSeconds())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -49,7 +52,8 @@ IMaterial::BindParameters::BindParameters(::GPUContext* context, const ::RenderC
|
||||
: GPUContext(context)
|
||||
, RenderContext(renderContext)
|
||||
, DrawCall(&drawCall)
|
||||
, TimeParam(Time::Draw.UnscaledTime.GetTotalSeconds())
|
||||
, Time(Time::Draw.Time.GetTotalSeconds())
|
||||
, UnscaledTime(Time::Draw.UnscaledTime.GetTotalSeconds())
|
||||
, Instanced(instanced)
|
||||
{
|
||||
}
|
||||
@@ -77,7 +81,8 @@ void IMaterial::BindParameters::BindViewData()
|
||||
cb.ViewPos = view.Position;
|
||||
cb.ViewFar = view.Far;
|
||||
cb.ViewDir = view.Direction;
|
||||
cb.TimeParam = TimeParam;
|
||||
cb.TimeParam = Time;
|
||||
cb.UnscaledTimeParam = UnscaledTime;
|
||||
cb.ViewInfo = view.ViewInfo;
|
||||
cb.ScreenSize = view.ScreenSize;
|
||||
cb.TemporalAAJitter = view.TemporalAAJitter;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/// <summary>
|
||||
/// Current materials shader version.
|
||||
/// </summary>
|
||||
#define MATERIAL_GRAPH_VERSION 173
|
||||
#define MATERIAL_GRAPH_VERSION 174
|
||||
|
||||
class Material;
|
||||
class GPUShader;
|
||||
|
||||
@@ -21,6 +21,8 @@ PACK_STRUCT(struct PostFxMaterialShaderData {
|
||||
Float4 ScreenSize;
|
||||
Float4 TemporalAAJitter;
|
||||
Matrix InverseViewProjectionMatrix;
|
||||
Float3 ViewPadding0;
|
||||
float UnscaledTimeParam;
|
||||
});
|
||||
|
||||
void PostFxMaterialShader::Bind(BindParameters& params)
|
||||
@@ -51,7 +53,8 @@ void PostFxMaterialShader::Bind(BindParameters& params)
|
||||
materialData->ViewPos = view.Position;
|
||||
materialData->ViewFar = view.Far;
|
||||
materialData->ViewDir = view.Direction;
|
||||
materialData->TimeParam = params.TimeParam;
|
||||
materialData->TimeParam = params.Time;
|
||||
materialData->UnscaledTimeParam = params.UnscaledTime;
|
||||
materialData->ViewInfo = view.ViewInfo;
|
||||
materialData->ScreenSize = view.ScreenSize;
|
||||
materialData->TemporalAAJitter = view.TemporalAAJitter;
|
||||
|
||||
@@ -176,6 +176,14 @@ void AnimatedModel::GetNodeTransformation(const StringView& nodeName, Matrix& no
|
||||
GetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace);
|
||||
}
|
||||
|
||||
void AnimatedModel::GetNodeTransformation(Array<NodeTransformation>& nodeTransformations, bool worldSpace) const
|
||||
{
|
||||
for (NodeTransformation& item : nodeTransformations)
|
||||
{
|
||||
GetNodeTransformation(item.NodeIndex, item.NodeMatrix, worldSpace);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedModel::SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTransformation, bool worldSpace)
|
||||
{
|
||||
if (GraphInstance.NodesPose.IsEmpty())
|
||||
@@ -193,6 +201,33 @@ void AnimatedModel::SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTra
|
||||
OnAnimationUpdated();
|
||||
}
|
||||
|
||||
void AnimatedModel::SetNodeTransformation(const Array<NodeTransformation>& nodeTransformations, bool worldSpace)
|
||||
{
|
||||
if (GraphInstance.NodesPose.IsEmpty())
|
||||
const_cast<AnimatedModel*>(this)->PreInitSkinningData(); // Ensure to have valid nodes pose to return
|
||||
|
||||
// Calculate it once, outside loop
|
||||
Matrix invWorld;
|
||||
if (worldSpace)
|
||||
{
|
||||
Matrix world;
|
||||
GetLocalToWorldMatrix(world);
|
||||
Matrix::Invert(world, invWorld);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodeTransformations.Count(); i++)
|
||||
{
|
||||
int nodeIndex = nodeTransformations[i].NodeIndex;
|
||||
CHECK(nodeIndex >= 0 && nodeIndex < GraphInstance.NodesPose.Count());
|
||||
GraphInstance.NodesPose[nodeIndex] = nodeTransformations[i].NodeMatrix;
|
||||
if (worldSpace)
|
||||
{
|
||||
GraphInstance.NodesPose[nodeIndex] = GraphInstance.NodesPose[nodeIndex] * invWorld;
|
||||
}
|
||||
}
|
||||
OnAnimationUpdated();
|
||||
}
|
||||
|
||||
void AnimatedModel::SetNodeTransformation(const StringView& nodeName, const Matrix& nodeTransformation, bool worldSpace)
|
||||
{
|
||||
SetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace);
|
||||
@@ -821,7 +856,10 @@ void AnimatedModel::OnAnimationUpdated_Async()
|
||||
_skinningData.OnDataChanged(!PerBoneMotionBlur);
|
||||
}
|
||||
|
||||
UpdateBounds();
|
||||
if (UpdateWhenOffscreen)
|
||||
{
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedModel::OnAnimationUpdated_Sync()
|
||||
|
||||
@@ -18,6 +18,24 @@ class FLAXENGINE_API AnimatedModel : public ModelInstanceActor
|
||||
DECLARE_SCENE_OBJECT(AnimatedModel);
|
||||
friend class AnimationsSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Keeps the data of a Node and its relevant Transform Matrix together when passing it between functions.
|
||||
/// </summary>
|
||||
API_STRUCT() struct NodeTransformation
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(NodeTransformation);
|
||||
|
||||
/// <summary>
|
||||
/// The index of the node in the node hierarchy.
|
||||
/// </summary>
|
||||
API_FIELD() uint32 NodeIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The transformation matrix of the node
|
||||
/// </summary>
|
||||
API_FIELD() Matrix NodeMatrix;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Describes the animation graph updates frequency for the animated model.
|
||||
/// </summary>
|
||||
@@ -236,6 +254,14 @@ public:
|
||||
/// <param name="worldSpace">True if convert matrices into world-space, otherwise returned values will be in local-space of the actor.</param>
|
||||
API_FUNCTION() void GetNodeTransformation(const StringView& nodeName, API_PARAM(Out) Matrix& nodeTransformation, bool worldSpace = false) const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the node final transformation for a series of nodes.
|
||||
/// </summary>
|
||||
/// <param name="nodeTransformations">The series of nodes that will be returned</param>
|
||||
/// <param name="worldSpace">True if convert matrices into world-space, otherwise returned values will be in local-space of the actor.</param>
|
||||
/// <returns></returns>
|
||||
API_FUNCTION() void GetNodeTransformation(API_PARAM(Ref) Array<NodeTransformation>& nodeTransformations, bool worldSpace = false) const;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the node final transformation. If multiple nodes are to be set within a frame, do not use set worldSpace to true, and do the conversion yourself to avoid recalculation of inv matrices.
|
||||
/// </summary>
|
||||
@@ -252,6 +278,14 @@ public:
|
||||
/// <param name="worldSpace">True if convert matrices from world-space, otherwise values will be in local-space of the actor.</param>
|
||||
API_FUNCTION() void SetNodeTransformation(const StringView& nodeName, const Matrix& nodeTransformation, bool worldSpace = false);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a group of nodes final transformation.
|
||||
/// </summary>
|
||||
/// <param name="nodeTransformations">Array of the final node transformation matrix.</param>
|
||||
/// <param name="worldSpace">True if convert matrices from world-space, otherwise values will be in local-space of the actor.</param>
|
||||
/// <returns></returns>
|
||||
API_FUNCTION() void SetNodeTransformation(const Array<NodeTransformation>& nodeTransformations, bool worldSpace = false);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the closest node to a given location.
|
||||
/// </summary>
|
||||
|
||||
@@ -546,6 +546,19 @@ void ParticleEffect::OnParticleSystemModified()
|
||||
}
|
||||
|
||||
void ParticleEffect::OnParticleSystemLoaded()
|
||||
{
|
||||
ApplyModifiedParameters();
|
||||
#if USE_EDITOR
|
||||
// When one of the emitters gets edited, cached parameters need to be applied
|
||||
auto& emitters = ParticleSystem.Get()->Emitters;
|
||||
for (auto& emitter : emitters)
|
||||
{
|
||||
emitter.Loaded.BindUnique<ParticleEffect, &ParticleEffect::OnParticleEmitterLoaded>(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ParticleEffect::OnParticleEmitterLoaded()
|
||||
{
|
||||
ApplyModifiedParameters();
|
||||
}
|
||||
@@ -823,6 +836,10 @@ void ParticleEffect::OnActiveInTreeChanged()
|
||||
CacheModifiedParameters();
|
||||
Instance.ClearState();
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyModifiedParameters();
|
||||
}
|
||||
}
|
||||
|
||||
void ParticleEffect::OnTransformChanged()
|
||||
|
||||
@@ -394,6 +394,7 @@ private:
|
||||
void ApplyModifiedParameters();
|
||||
void OnParticleSystemModified();
|
||||
void OnParticleSystemLoaded();
|
||||
void OnParticleEmitterLoaded();
|
||||
|
||||
public:
|
||||
// [Actor]
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
#include "Engine/Platform/File.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
#include "Engine/Core/Types/TimeSpan.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Utilities/StringConverter.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -18,281 +14,9 @@
|
||||
#include <stdio.h>
|
||||
#include <cerrno>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
const DateTime UnixEpoch(1970, 1, 1);
|
||||
|
||||
bool AppleFileSystem::CreateDirectory(const StringView& path)
|
||||
{
|
||||
const StringAsANSI<> pathAnsi(*path, path.Length());
|
||||
|
||||
// Skip if already exists
|
||||
struct stat fileInfo;
|
||||
if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recursively do it all again for the parent directory, if any
|
||||
const int32 slashIndex = path.FindLast('/');
|
||||
if (slashIndex > 1)
|
||||
{
|
||||
if (CreateDirectory(path.Substring(0, slashIndex)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the last directory on the path (the recursive calls will have taken care of the parent directories by now)
|
||||
return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST;
|
||||
}
|
||||
|
||||
bool DeletePathTree(const char* path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Recursively remove a nested directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (DeletePathTree(full_path))
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove a file object
|
||||
if (unlink(full_path) != 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove the devastated directory and close the object of it
|
||||
if (rmdir(path) != 0)
|
||||
return true;
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::DeleteDirectory(const String& path, bool deleteContents)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (deleteContents)
|
||||
return DeletePathTree(pathANSI.Get());
|
||||
return rmdir(pathANSI.Get()) != 0;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::DirectoryExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISDIR(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::DirectoryGetFiles(Array<String>& results, const String& path, const Char* searchPattern, DirectorySearchOption option)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const StringAsANSI<> searchPatternANSI(searchPattern);
|
||||
|
||||
// Check if use only top directory
|
||||
if (option == DirectorySearchOption::TopDirectoryOnly)
|
||||
return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
}
|
||||
|
||||
bool AppleFileSystem::GetChildDirectories(Array<String>& results, const String& path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const char* pathStr = pathANSI.Get();
|
||||
|
||||
// Stat for the path
|
||||
stat(pathStr, &statPath);
|
||||
|
||||
// If path does not exist or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(pathStr)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(pathStr);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, pathStr);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Add directory
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::FileExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISREG(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::DeleteFile(const StringView& path)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
return unlink(pathANSI.Get()) != 0;
|
||||
}
|
||||
|
||||
uint64 AppleFileSystem::GetFileSize(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
fileInfo.st_size = 0;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
// Check for directories
|
||||
if (S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
fileInfo.st_size = 0;
|
||||
}
|
||||
}
|
||||
return fileInfo.st_size;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::IsReadOnly(const StringView& path)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (access(pathANSI.Get(), W_OK) == -1)
|
||||
{
|
||||
return errno == EACCES;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::SetReadOnly(const StringView& path, bool isReadOnly)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
struct stat fileInfo;
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
if (isReadOnly)
|
||||
{
|
||||
fileInfo.st_mode &= ~S_IWUSR;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileInfo.st_mode |= S_IWUSR;
|
||||
}
|
||||
return chmod(pathANSI.Get(), fileInfo.st_mode) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite)
|
||||
{
|
||||
if (!overwrite && FileExists(dst))
|
||||
{
|
||||
// Already exists
|
||||
return true;
|
||||
}
|
||||
|
||||
if (overwrite)
|
||||
{
|
||||
unlink(StringAsANSI<>(*dst, dst.Length()).Get());
|
||||
}
|
||||
if (rename(StringAsANSI<>(*src, src.Length()).Get(), StringAsANSI<>(*dst, dst.Length()).Get()) != 0)
|
||||
{
|
||||
if (errno == EXDEV)
|
||||
{
|
||||
if (!CopyFile(dst, src))
|
||||
{
|
||||
unlink(StringAsANSI<>(*src, src.Length()).Get());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::CopyFile(const StringView& dst, const StringView& src)
|
||||
{
|
||||
const StringAsANSI<> srcANSI(*src, src.Length());
|
||||
@@ -352,156 +76,6 @@ out_error:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::getFilesFromDirectoryTop(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
size_t pathLength;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
DIR* dir = opendir(path);
|
||||
if (dir == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, path);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for file
|
||||
if (S_ISREG(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Validate with filter
|
||||
const int32 fullPathLength = StringUtils::Length(fullPath);
|
||||
const int32 searchPatternLength = StringUtils::Length(searchPattern);
|
||||
if (searchPatternLength == 0 || StringUtils::Compare(searchPattern, "*") == 0)
|
||||
{
|
||||
// All files
|
||||
}
|
||||
else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0)
|
||||
{
|
||||
// Path ending
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: implement all cases in a generic way
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add file
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppleFileSystem::getFilesFromDirectoryAll(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
// Find all files in this directory
|
||||
getFilesFromDirectoryTop(results, path, searchPattern);
|
||||
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (getFilesFromDirectoryAll(results, full_path, searchPattern))
|
||||
{
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime AppleFileSystem::GetFileLastEditTime(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) == -1)
|
||||
{
|
||||
return DateTime::MinValue();
|
||||
}
|
||||
|
||||
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
|
||||
return UnixEpoch + timeSinceEpoch;
|
||||
}
|
||||
|
||||
void AppleFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& result)
|
||||
{
|
||||
String home;
|
||||
|
||||
@@ -4,33 +4,17 @@
|
||||
|
||||
#if PLATFORM_MAC || PLATFORM_IOS
|
||||
|
||||
#include "Engine/Platform/Base/FileSystemBase.h"
|
||||
#include "Engine/Platform/Unix/UnixFileSystem.h"
|
||||
|
||||
/// <summary>
|
||||
/// Apple platform implementation of filesystem service.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API AppleFileSystem : public FileSystemBase
|
||||
class FLAXENGINE_API AppleFileSystem : public UnixFileSystem
|
||||
{
|
||||
public:
|
||||
// [FileSystemBase]
|
||||
static bool CreateDirectory(const StringView& path);
|
||||
static bool DeleteDirectory(const String& path, bool deleteContents = true);
|
||||
static bool DirectoryExists(const StringView& path);
|
||||
static bool DirectoryGetFiles(Array<String, HeapAllocation>& results, const String& path, const Char* searchPattern = TEXT("*"), DirectorySearchOption option = DirectorySearchOption::AllDirectories);
|
||||
static bool GetChildDirectories(Array<String, HeapAllocation>& results, const String& path);
|
||||
static bool FileExists(const StringView& path);
|
||||
static bool DeleteFile(const StringView& path);
|
||||
static uint64 GetFileSize(const StringView& path);
|
||||
static bool IsReadOnly(const StringView& path);
|
||||
static bool SetReadOnly(const StringView& path, bool isReadOnly);
|
||||
static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false);
|
||||
static bool CopyFile(const StringView& dst, const StringView& src);
|
||||
static DateTime GetFileLastEditTime(const StringView& path);
|
||||
static void GetSpecialFolderPath(const SpecialFolder type, String& result);
|
||||
|
||||
private:
|
||||
static bool getFilesFromDirectoryTop(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
static bool getFilesFromDirectoryAll(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,22 +4,16 @@
|
||||
|
||||
#include "LinuxFileSystem.h"
|
||||
#include "Engine/Platform/File.h"
|
||||
#include "Engine/Platform/StringUtils.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/StringBuilder.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
#include "Engine/Core/Types/TimeSpan.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Utilities/StringConverter.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <cerrno>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
@@ -165,280 +159,6 @@ bool LinuxFileSystem::ShowFileExplorer(const StringView& path)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::CreateDirectory(const StringView& path)
|
||||
{
|
||||
const StringAsUTF8<> pathAnsi(*path, path.Length());
|
||||
|
||||
// Skip if already exists
|
||||
struct stat fileInfo;
|
||||
if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recursively do it all again for the parent directory, if any
|
||||
const int32 slashIndex = path.FindLast('/');
|
||||
if (slashIndex > 1)
|
||||
{
|
||||
if (CreateDirectory(path.Substring(0, slashIndex)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the last directory on the path (the recursive calls will have taken care of the parent directories by now)
|
||||
return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST;
|
||||
}
|
||||
|
||||
bool DeleteUnixPathTree(const char* path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Recursively remove a nested directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (DeleteUnixPathTree(full_path))
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove a file object
|
||||
if (unlink(full_path) != 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove the devastated directory and close the object of it
|
||||
if (rmdir(path) != 0)
|
||||
return true;
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::DeleteDirectory(const String& path, bool deleteContents)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (deleteContents)
|
||||
{
|
||||
return DeleteUnixPathTree(pathANSI.Get());
|
||||
}
|
||||
else
|
||||
{
|
||||
return rmdir(pathANSI.Get()) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::DirectoryExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISDIR(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::DirectoryGetFiles(Array<String>& results, const String& path, const Char* searchPattern, DirectorySearchOption option)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
const StringAsUTF8<> searchPatternANSI(searchPattern);
|
||||
|
||||
// Check if use only top directory
|
||||
if (option == DirectorySearchOption::TopDirectoryOnly)
|
||||
return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::GetChildDirectories(Array<String>& results, const String& path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
const char* pathStr = pathANSI.Get();
|
||||
|
||||
// Stat for the path
|
||||
stat(pathStr, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(pathStr)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(pathStr);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, pathStr);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Add directory
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::FileExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISREG(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::DeleteFile(const StringView& path)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
return unlink(pathANSI.Get()) != 0;
|
||||
}
|
||||
|
||||
uint64 LinuxFileSystem::GetFileSize(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
fileInfo.st_size = 0;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
// Check for directories
|
||||
if (S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
fileInfo.st_size = 0;
|
||||
}
|
||||
}
|
||||
return fileInfo.st_size;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::IsReadOnly(const StringView& path)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (access(pathANSI.Get(), W_OK) == -1)
|
||||
{
|
||||
return errno == EACCES;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::SetReadOnly(const StringView& path, bool isReadOnly)
|
||||
{
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
struct stat fileInfo;
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
if (isReadOnly)
|
||||
{
|
||||
fileInfo.st_mode &= ~S_IWUSR;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileInfo.st_mode |= S_IWUSR;
|
||||
}
|
||||
return chmod(pathANSI.Get(), fileInfo.st_mode) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite)
|
||||
{
|
||||
if (!overwrite && FileExists(dst))
|
||||
{
|
||||
// Already exists
|
||||
return true;
|
||||
}
|
||||
|
||||
if (overwrite)
|
||||
{
|
||||
unlink(StringAsUTF8<>(*dst, dst.Length()).Get());
|
||||
}
|
||||
if (rename(StringAsUTF8<>(*src, src.Length()).Get(), StringAsUTF8<>(*dst, dst.Length()).Get()) != 0)
|
||||
{
|
||||
if (errno == EXDEV)
|
||||
{
|
||||
if (!CopyFile(dst, src))
|
||||
{
|
||||
unlink(StringAsUTF8<>(*src, src.Length()).Get());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::CopyFile(const StringView& dst, const StringView& src)
|
||||
{
|
||||
const StringAsUTF8<> srcANSI(*src, src.Length());
|
||||
@@ -612,156 +332,6 @@ bool LinuxFileSystem::UrnEncodePath(const char *path, char *result, const int ma
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::getFilesFromDirectoryTop(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
size_t pathLength;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
DIR* dir = opendir(path);
|
||||
if (dir == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, path);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for file
|
||||
if (S_ISREG(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Validate with filter
|
||||
const int32 fullPathLength = StringUtils::Length(fullPath);
|
||||
const int32 searchPatternLength = StringUtils::Length(searchPattern);
|
||||
if (searchPatternLength == 0 || StringUtils::Compare(searchPattern, "*") == 0)
|
||||
{
|
||||
// All files
|
||||
}
|
||||
else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0)
|
||||
{
|
||||
// Path ending
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: implement all cases in a generic way
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add file
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxFileSystem::getFilesFromDirectoryAll(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
// Find all files in this directory
|
||||
getFilesFromDirectoryTop(results, path, searchPattern);
|
||||
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (getFilesFromDirectoryAll(results, full_path, searchPattern))
|
||||
{
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime LinuxFileSystem::GetFileLastEditTime(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) == -1)
|
||||
{
|
||||
return DateTime::MinValue();
|
||||
}
|
||||
|
||||
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
|
||||
return UnixEpoch + timeSinceEpoch;
|
||||
}
|
||||
|
||||
void LinuxFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& result)
|
||||
{
|
||||
const String& home = LinuxPlatform::GetHomeDirectory();
|
||||
|
||||
@@ -4,38 +4,24 @@
|
||||
|
||||
#if PLATFORM_LINUX
|
||||
|
||||
#include "Engine/Platform/Base/FileSystemBase.h"
|
||||
#include "Engine/Platform/Unix/UnixFileSystem.h"
|
||||
|
||||
/// <summary>
|
||||
/// Linux platform implementation of filesystem service.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API LinuxFileSystem : public FileSystemBase
|
||||
class FLAXENGINE_API LinuxFileSystem : public UnixFileSystem
|
||||
{
|
||||
public:
|
||||
// [FileSystemBase]
|
||||
static bool ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array<String, HeapAllocation>& filenames);
|
||||
static bool ShowBrowseFolderDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& title, String& path);
|
||||
static bool ShowFileExplorer(const StringView& path);
|
||||
static bool CreateDirectory(const StringView& path);
|
||||
static bool DeleteDirectory(const String& path, bool deleteContents = true);
|
||||
static bool DirectoryExists(const StringView& path);
|
||||
static bool DirectoryGetFiles(Array<String, HeapAllocation>& results, const String& path, const Char* searchPattern = TEXT("*"), DirectorySearchOption option = DirectorySearchOption::AllDirectories);
|
||||
static bool GetChildDirectories(Array<String, HeapAllocation>& results, const String& path);
|
||||
static bool FileExists(const StringView& path);
|
||||
static bool DeleteFile(const StringView& path);
|
||||
static bool MoveFileToRecycleBin(const StringView& path);
|
||||
static uint64 GetFileSize(const StringView& path);
|
||||
static bool IsReadOnly(const StringView& path);
|
||||
static bool SetReadOnly(const StringView& path, bool isReadOnly);
|
||||
static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false);
|
||||
static bool CopyFile(const StringView& dst, const StringView& src);
|
||||
static DateTime GetFileLastEditTime(const StringView& path);
|
||||
static bool MoveFileToRecycleBin(const StringView& path);
|
||||
static void GetSpecialFolderPath(const SpecialFolder type, String& result);
|
||||
|
||||
private:
|
||||
static bool UrnEncodePath(const char *path, char *result, int maxLength);
|
||||
static bool getFilesFromDirectoryTop(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
static bool getFilesFromDirectoryAll(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
static String getBaseName(const StringView& path);
|
||||
static String getNameWithoutExtension(const StringView& path);
|
||||
};
|
||||
|
||||
470
Source/Engine/Platform/Unix/UnixFileSystem.cpp
Normal file
470
Source/Engine/Platform/Unix/UnixFileSystem.cpp
Normal file
@@ -0,0 +1,470 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#if PLATFORM_UNIX
|
||||
|
||||
#include "UnixFileSystem.h"
|
||||
#include "Engine/Platform/File.h"
|
||||
#include "Engine/Core/Types/TimeSpan.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Utilities/StringConverter.h"
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <cerrno>
|
||||
#include <dirent.h>
|
||||
|
||||
#if PLATFORM_MAC || PLATFORM_IOS
|
||||
typedef StringAsANSI<> UnixString;
|
||||
#else
|
||||
typedef StringAsUTF8<> UnixString;
|
||||
#endif
|
||||
|
||||
const DateTime UnixEpoch(1970, 1, 1);
|
||||
|
||||
bool UnixFileSystem::CreateDirectory(const StringView& path)
|
||||
{
|
||||
const UnixString pathAnsi(*path, path.Length());
|
||||
|
||||
// Skip if already exists
|
||||
struct stat fileInfo;
|
||||
if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recursively do it all again for the parent directory, if any
|
||||
const int32 slashIndex = path.FindLast('/');
|
||||
if (slashIndex > 1)
|
||||
{
|
||||
if (CreateDirectory(path.Substring(0, slashIndex)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the last directory on the path (the recursive calls will have taken care of the parent directories by now)
|
||||
return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST;
|
||||
}
|
||||
|
||||
bool DeleteUnixPathTree(const char* path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Recursively remove a nested directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (DeleteUnixPathTree(full_path))
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove a file object
|
||||
if (unlink(full_path) != 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove the devastated directory and close the object of it
|
||||
if (rmdir(path) != 0)
|
||||
return true;
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::DeleteDirectory(const String& path, bool deleteContents)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (deleteContents)
|
||||
{
|
||||
return DeleteUnixPathTree(pathANSI.Get());
|
||||
}
|
||||
else
|
||||
{
|
||||
return rmdir(pathANSI.Get()) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool UnixFileSystem::DirectoryExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISDIR(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::DirectoryGetFiles(Array<String>& results, const String& path, const Char* searchPattern, DirectorySearchOption option)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
const UnixString searchPatternANSI(searchPattern);
|
||||
|
||||
// Check if use only top directory
|
||||
if (option == DirectorySearchOption::TopDirectoryOnly)
|
||||
return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get());
|
||||
}
|
||||
|
||||
bool UnixFileSystem::GetChildDirectories(Array<String>& results, const String& path)
|
||||
{
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
const char* pathStr = pathANSI.Get();
|
||||
|
||||
// Stat for the path
|
||||
stat(pathStr, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(pathStr)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(pathStr);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, pathStr);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Add directory
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::FileExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISREG(fileInfo.st_mode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::DeleteFile(const StringView& path)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
return unlink(pathANSI.Get()) != 0;
|
||||
}
|
||||
|
||||
uint64 UnixFileSystem::GetFileSize(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
fileInfo.st_size = 0;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
// Check for directories
|
||||
if (S_ISDIR(fileInfo.st_mode))
|
||||
{
|
||||
fileInfo.st_size = 0;
|
||||
}
|
||||
}
|
||||
return fileInfo.st_size;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::IsReadOnly(const StringView& path)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (access(pathANSI.Get(), W_OK) == -1)
|
||||
{
|
||||
return errno == EACCES;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::SetReadOnly(const StringView& path, bool isReadOnly)
|
||||
{
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
struct stat fileInfo;
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
if (isReadOnly)
|
||||
{
|
||||
fileInfo.st_mode &= ~S_IWUSR;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileInfo.st_mode |= S_IWUSR;
|
||||
}
|
||||
return chmod(pathANSI.Get(), fileInfo.st_mode) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite)
|
||||
{
|
||||
if (!overwrite && FileExists(dst))
|
||||
{
|
||||
// Already exists
|
||||
return true;
|
||||
}
|
||||
|
||||
if (overwrite)
|
||||
{
|
||||
unlink(UnixString(*dst, dst.Length()).Get());
|
||||
}
|
||||
if (rename(UnixString(*src, src.Length()).Get(), UnixString(*dst, dst.Length()).Get()) != 0)
|
||||
{
|
||||
if (errno == EXDEV)
|
||||
{
|
||||
if (!CopyFile(dst, src))
|
||||
{
|
||||
unlink(UnixString(*src, src.Length()).Get());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::getFilesFromDirectoryTop(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
size_t pathLength;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
DIR* dir = opendir(path);
|
||||
if (dir == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char fullPath[256];
|
||||
const int32 pathLength = strlen(entry->d_name);
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
|
||||
strcpy(fullPath, path);
|
||||
strcat(fullPath, "/");
|
||||
strcat(fullPath, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(fullPath, &statEntry);
|
||||
|
||||
// Check for file
|
||||
if (S_ISREG(statEntry.st_mode) != 0)
|
||||
{
|
||||
// Validate with filter
|
||||
const int32 fullPathLength = StringUtils::Length(fullPath);
|
||||
const int32 searchPatternLength = StringUtils::Length(searchPattern);
|
||||
if (searchPatternLength == 0 ||
|
||||
StringUtils::Compare(searchPattern, "*") == 0 ||
|
||||
StringUtils::Compare(searchPattern, "*.*") == 0)
|
||||
{
|
||||
// All files
|
||||
}
|
||||
else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0)
|
||||
{
|
||||
// Path ending
|
||||
}
|
||||
else if (searchPattern[0] == '*' && searchPatternLength > 2 && searchPattern[searchPatternLength-1] == '*')
|
||||
{
|
||||
// Contains pattern
|
||||
bool match = false;
|
||||
for (int32 i = 0; i < pathLength - searchPatternLength - 1; i++)
|
||||
{
|
||||
int32 len = Math::Min(searchPatternLength - 2, pathLength - i);
|
||||
if (StringUtils::Compare(&entry->d_name[i], &searchPattern[1], len) == 0)
|
||||
{
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match)
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: implement all cases in a generic way
|
||||
LOG(Warning, "DirectoryGetFiles: Wildcard filter is not implemented");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add file
|
||||
results.Add(String(fullPath));
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnixFileSystem::getFilesFromDirectoryAll(Array<String>& results, const char* path, const char* searchPattern)
|
||||
{
|
||||
// Find all files in this directory
|
||||
getFilesFromDirectoryTop(results, path, searchPattern);
|
||||
|
||||
size_t pathLength;
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
|
||||
// Stat for the path
|
||||
stat(path, &statPath);
|
||||
|
||||
// If path does not exists or is not dir - exit with status -1
|
||||
if (S_ISDIR(statPath.st_mode) == 0)
|
||||
{
|
||||
// Is not directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not possible to read the directory for this user
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
{
|
||||
// Cannot open directory
|
||||
return true;
|
||||
}
|
||||
|
||||
// The length of the path
|
||||
pathLength = strlen(path);
|
||||
|
||||
// Iteration through entries in the directory
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip entries "." and ".."
|
||||
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
|
||||
continue;
|
||||
|
||||
// Determinate a full path of an entry
|
||||
char full_path[256];
|
||||
ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
|
||||
strcpy(full_path, path);
|
||||
strcat(full_path, "/");
|
||||
strcat(full_path, entry->d_name);
|
||||
|
||||
// Stat for the entry
|
||||
stat(full_path, &statEntry);
|
||||
|
||||
// Check for directory
|
||||
if (S_ISDIR(statEntry.st_mode) != 0)
|
||||
{
|
||||
if (getFilesFromDirectoryAll(results, full_path, searchPattern))
|
||||
{
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime UnixFileSystem::GetFileLastEditTime(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const UnixString pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) == -1)
|
||||
{
|
||||
return DateTime::MinValue();
|
||||
}
|
||||
|
||||
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
|
||||
return UnixEpoch + timeSinceEpoch;
|
||||
}
|
||||
|
||||
#endif
|
||||
35
Source/Engine/Platform/Unix/UnixFileSystem.h
Normal file
35
Source/Engine/Platform/Unix/UnixFileSystem.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if PLATFORM_UNIX
|
||||
|
||||
#include "Engine/Platform/Base/FileSystemBase.h"
|
||||
|
||||
/// <summary>
|
||||
/// Unix platform implementation of filesystem service.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API UnixFileSystem : public FileSystemBase
|
||||
{
|
||||
public:
|
||||
// [FileSystemBase]
|
||||
static bool CreateDirectory(const StringView& path);
|
||||
static bool DeleteDirectory(const String& path, bool deleteContents = true);
|
||||
static bool DirectoryExists(const StringView& path);
|
||||
static bool DirectoryGetFiles(Array<String, HeapAllocation>& results, const String& path, const Char* searchPattern = TEXT("*"), DirectorySearchOption option = DirectorySearchOption::AllDirectories);
|
||||
static bool GetChildDirectories(Array<String, HeapAllocation>& results, const String& path);
|
||||
static bool FileExists(const StringView& path);
|
||||
static bool DeleteFile(const StringView& path);
|
||||
static bool MoveFileToRecycleBin(const StringView& path);
|
||||
static uint64 GetFileSize(const StringView& path);
|
||||
static bool IsReadOnly(const StringView& path);
|
||||
static bool SetReadOnly(const StringView& path, bool isReadOnly);
|
||||
static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false);
|
||||
static DateTime GetFileLastEditTime(const StringView& path);
|
||||
|
||||
private:
|
||||
static bool getFilesFromDirectoryTop(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
static bool getFilesFromDirectoryAll(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -434,6 +434,7 @@ void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* light
|
||||
PROFILE_GPU_CPU("Decals");
|
||||
auto context = GPUDevice::Instance->GetMainContext();
|
||||
auto buffers = renderContext.Buffers;
|
||||
GPUTextureView* depthBuffer = EnumHasAnyFlags(buffers->DepthBuffer->Flags(), GPUTextureFlags::ReadOnlyDepthView) ? buffers->DepthBuffer->ViewReadOnlyDepth() : nullptr;
|
||||
|
||||
// Sort decals from the lowest order to the highest order
|
||||
Sorting::QuickSort(decals.Get(), decals.Count(), &SortDecal);
|
||||
@@ -484,22 +485,22 @@ void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* light
|
||||
count++;
|
||||
targetBuffers[2] = buffers->GBuffer1->View();
|
||||
}
|
||||
context->SetRenderTarget(nullptr, ToSpan(targetBuffers, count));
|
||||
context->SetRenderTarget(depthBuffer, ToSpan(targetBuffers, count));
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Stain:
|
||||
{
|
||||
context->SetRenderTarget(buffers->GBuffer0->View());
|
||||
context->SetRenderTarget(depthBuffer, buffers->GBuffer0->View());
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Normal:
|
||||
{
|
||||
context->SetRenderTarget(buffers->GBuffer1->View());
|
||||
context->SetRenderTarget(depthBuffer, buffers->GBuffer1->View());
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Emissive:
|
||||
{
|
||||
context->SetRenderTarget(lightBuffer);
|
||||
context->SetRenderTarget(depthBuffer, lightBuffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ namespace
|
||||
MMethod* _method_LateFixedUpdate = nullptr;
|
||||
MMethod* _method_Draw = nullptr;
|
||||
MMethod* _method_Exit = nullptr;
|
||||
Array<Function<void()>> UpdateActions;
|
||||
Dictionary<StringAnsi, BinaryModule*, InlinedAllocation<64>> _nonNativeModules;
|
||||
#if USE_EDITOR
|
||||
bool LastBinariesLoadTriggeredCompilation = false;
|
||||
@@ -242,6 +243,27 @@ void ScriptingService::Update()
|
||||
PROFILE_CPU_NAMED("Scripting::Update");
|
||||
INVOKE_EVENT(Update);
|
||||
|
||||
// Flush update actions
|
||||
_objectsLocker.Lock();
|
||||
int32 count = UpdateActions.Count();
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
UpdateActions[i]();
|
||||
}
|
||||
int32 newlyAdded = UpdateActions.Count() - count;
|
||||
if (newlyAdded == 0)
|
||||
UpdateActions.Clear();
|
||||
else
|
||||
{
|
||||
// Someone added another action within current callback
|
||||
Array<Function<void()>> tmp;
|
||||
for (int32 i = newlyAdded; i < UpdateActions.Count(); i++)
|
||||
tmp.Add(UpdateActions[i]);
|
||||
UpdateActions.Clear();
|
||||
UpdateActions.Add(tmp);
|
||||
}
|
||||
_objectsLocker.Unlock();
|
||||
|
||||
#ifdef USE_NETCORE
|
||||
// Force GC to run in background periodically to avoid large blocking collections causing hitches
|
||||
if (Time::Update.TicksCount % 60 == 0)
|
||||
@@ -303,6 +325,13 @@ void Scripting::ProcessBuildInfoPath(String& path, const String& projectFolderPa
|
||||
path = projectFolderPath / path;
|
||||
}
|
||||
|
||||
void Scripting::InvokeOnUpdate(const Function<void()>& action)
|
||||
{
|
||||
_objectsLocker.Lock();
|
||||
UpdateActions.Add(action);
|
||||
_objectsLocker.Unlock();
|
||||
}
|
||||
|
||||
bool Scripting::LoadBinaryModules(const String& path, const String& projectFolderPath)
|
||||
{
|
||||
PROFILE_CPU_NAMED("LoadBinaryModules");
|
||||
|
||||
@@ -230,6 +230,11 @@ public:
|
||||
|
||||
static void ProcessBuildInfoPath(String& path, const String& projectFolderPath);
|
||||
|
||||
/// <summary>
|
||||
/// Calls the given action on the next scripting update.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to invoke.</param>
|
||||
static void InvokeOnUpdate(const Function<void()>& action);
|
||||
private:
|
||||
|
||||
static bool LoadBinaryModules(const String& path, const String& projectFolderPath);
|
||||
|
||||
@@ -61,7 +61,11 @@ void MemoryReadStream::ReadBytes(void* data, uint32 bytes)
|
||||
{
|
||||
if (bytes > 0)
|
||||
{
|
||||
ASSERT(data && GetLength() - GetPosition() >= bytes);
|
||||
if (!data || GetLength() - GetPosition() < bytes)
|
||||
{
|
||||
_hasError = true;
|
||||
return;
|
||||
}
|
||||
Platform::MemoryCopy(data, _position, bytes);
|
||||
_position += bytes;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B
|
||||
const bool isArray = texture->Type == MaterialParameterType::GPUTextureArray;
|
||||
const bool isVolume = texture->Type == MaterialParameterType::GPUTextureVolume;
|
||||
const bool isNormalMap = texture->Type == MaterialParameterType::NormalMap;
|
||||
const bool canUseSample = CanUseSample(_treeType);
|
||||
MaterialGraphBox* valueBox = parent->GetBox(1);
|
||||
|
||||
// Check if has variable assigned
|
||||
@@ -63,6 +62,16 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B
|
||||
// Check if hasn't been sampled during that tree eating
|
||||
if (valueBox->Cache.IsInvalid())
|
||||
{
|
||||
bool canUseSample = CanUseSample(_treeType);
|
||||
String mipLevel = TEXT("0");
|
||||
const auto layer = GetRootLayer();
|
||||
if (layer && layer->Domain == MaterialDomain::Decal && _treeType == MaterialTreeType::PixelShader)
|
||||
{
|
||||
// Decals use computed mip level due to ddx/ddy being unreliable
|
||||
canUseSample = false;
|
||||
mipLevel = String::Format(TEXT("CalculateTextureMipmap(input, {})"), texture->ShaderName);
|
||||
}
|
||||
|
||||
// Check if use custom UVs
|
||||
String uv;
|
||||
MaterialGraphBox* uvBox = parent->GetBox(0);
|
||||
@@ -94,10 +103,10 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B
|
||||
// Sample texture
|
||||
if (isNormalMap)
|
||||
{
|
||||
const Char* format = canUseSample ? TEXT("{0}.Sample({1}, {2}).xyz") : TEXT("{0}.SampleLevel({1}, {2}, 0).xyz");
|
||||
const Char* format = canUseSample ? TEXT("{0}.Sample({1}, {2}).xyz") : TEXT("{0}.SampleLevel({1}, {2}, {3}).xyz");
|
||||
|
||||
// Sample encoded normal map
|
||||
const String sampledValue = String::Format(format, texture->ShaderName, sampler, uv);
|
||||
const String sampledValue = String::Format(format, texture->ShaderName, sampler, uv, mipLevel);
|
||||
const auto normalVector = writeLocal(VariantType::Float3, sampledValue, parent);
|
||||
|
||||
// Decode normal vector
|
||||
@@ -123,12 +132,12 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B
|
||||
}
|
||||
else*/
|
||||
{
|
||||
format = canUseSample ? TEXT("{0}.Sample({1}, {2})") : TEXT("{0}.SampleLevel({1}, {2}, 0)");
|
||||
format = canUseSample ? TEXT("{0}.Sample({1}, {2})") : TEXT("{0}.SampleLevel({1}, {2}, {3})");
|
||||
}
|
||||
}
|
||||
|
||||
// Sample texture
|
||||
String sampledValue = String::Format(format, texture->ShaderName, sampler, uv, _ddx.Value, _ddy.Value);
|
||||
String sampledValue = String::Format(format, texture->ShaderName, sampler, uv, mipLevel);
|
||||
valueBox->Cache = writeLocal(VariantType::Float4, sampledValue, parent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,10 +48,8 @@ void MaterialGenerator::ProcessGroupTools(Box* box, Node* node, Value& value)
|
||||
}
|
||||
// Time
|
||||
case 3:
|
||||
{
|
||||
value = getTime;
|
||||
value = box->ID == 1 ? getUnscaledTime : getTime;
|
||||
break;
|
||||
}
|
||||
// Panner
|
||||
case 6:
|
||||
{
|
||||
|
||||
@@ -107,6 +107,7 @@ bool FeatureData::Init()
|
||||
|
||||
MaterialValue MaterialGenerator::getUVs(VariantType::Float2, TEXT("input.TexCoord"));
|
||||
MaterialValue MaterialGenerator::getTime(VariantType::Float, TEXT("TimeParam"));
|
||||
MaterialValue MaterialGenerator::getUnscaledTime(VariantType::Float, TEXT("UnscaledTimeParam"));
|
||||
MaterialValue MaterialGenerator::getNormal(VariantType::Float3, TEXT("input.TBN[2]"));
|
||||
MaterialValue MaterialGenerator::getNormalZero(VariantType::Float3, TEXT("float3(0, 0, 1)"));
|
||||
MaterialValue MaterialGenerator::getVertexColor(VariantType::Float4, TEXT("GetVertexColor(input)"));
|
||||
|
||||
@@ -120,7 +120,6 @@ private:
|
||||
MaterialValue _ddx, _ddy, _cameraVector;
|
||||
|
||||
public:
|
||||
|
||||
MaterialGenerator();
|
||||
~MaterialGenerator();
|
||||
|
||||
@@ -211,6 +210,7 @@ public:
|
||||
|
||||
static MaterialValue getUVs;
|
||||
static MaterialValue getTime;
|
||||
static MaterialValue getUnscaledTime;
|
||||
static MaterialValue getNormal;
|
||||
static MaterialValue getNormalZero;
|
||||
static MaterialValue getVertexColor;
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace FlaxEngine.GUI
|
||||
/// Initializes a new instance of the <see cref="MaterialBrush"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="material">The material.</param>
|
||||
public MaterialBrush(Material material)
|
||||
public MaterialBrush(MaterialBase material)
|
||||
{
|
||||
Material = material;
|
||||
}
|
||||
|
||||
@@ -134,6 +134,10 @@ namespace FlaxEngine.Utilities
|
||||
if (isLeadingSlash)
|
||||
Move();
|
||||
|
||||
// Dont process if wrong slash is used.
|
||||
if (c =='\\')
|
||||
return false;
|
||||
|
||||
// Parse tag
|
||||
bool result = ParseTag(ref tag, name);
|
||||
|
||||
@@ -204,6 +208,10 @@ namespace FlaxEngine.Utilities
|
||||
SkipWhitespace();
|
||||
while (Peek() != '>')
|
||||
{
|
||||
// Return false if start of new html tag is detected.
|
||||
if (Peek() == '<')
|
||||
return false;
|
||||
|
||||
if (Peek() == '/')
|
||||
{
|
||||
// Handle trailing forward slash
|
||||
|
||||
Reference in New Issue
Block a user