Merge remote-tracking branch 'origin/master' into sdl_platform

This commit is contained in:
2025-08-24 09:44:28 +03:00
99 changed files with 1300 additions and 1136 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -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)
{

View File

@@ -548,7 +548,7 @@ public:
/// <summary>
/// Instance of the empty string.
/// </summary>
static String Empty;
static const String Empty;
public:
/// <summary>

View File

@@ -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())

View File

@@ -219,7 +219,7 @@ public:
/// <summary>
/// Instance of the empty string.
/// </summary>
static StringView Empty;
static const StringView Empty;
public:
/// <summary>

View File

@@ -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))
{

View File

@@ -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);

View File

@@ -148,7 +148,7 @@ public:
const ::DrawCall* DrawCall = nullptr;
MaterialParamsLink* ParamsLink = nullptr;
void* CustomData = nullptr;
float TimeParam;
float Time, UnscaledTime;
bool Instanced = false;
/// <summary>

View File

@@ -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;

View File

@@ -10,7 +10,7 @@
/// <summary>
/// Current materials shader version.
/// </summary>
#define MATERIAL_GRAPH_VERSION 173
#define MATERIAL_GRAPH_VERSION 174
class Material;
class GPUShader;

View File

@@ -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;

View File

@@ -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()

View File

@@ -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>

View File

@@ -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()

View File

@@ -394,6 +394,7 @@ private:
void ApplyModifiedParameters();
void OnParticleSystemModified();
void OnParticleSystemLoaded();
void OnParticleEmitterLoaded();
public:
// [Actor]

View File

@@ -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;

View File

@@ -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

View File

@@ -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();

View File

@@ -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);
};

View 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

View 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

View File

@@ -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;
}
}

View File

@@ -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");

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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:
{

View File

@@ -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)"));

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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