Merge branch 'FlaxEngine:master' into visject_grid_snap

This commit is contained in:
Menotdan
2023-10-03 23:30:18 -04:00
committed by GitHub
42 changed files with 560 additions and 93 deletions

View File

@@ -4,7 +4,7 @@
#include "../Collections/Array.h"
#include "../Collections/Dictionary.h"
#include <functional>
#include "../Delegate.h"
class ArrayExtensions;
@@ -23,7 +23,6 @@ public:
/// <summary>
/// Gets the common key.
/// </summary>
/// <returns>The key.</returns>
FORCE_INLINE const TKey& GetKey() const
{
return _key;
@@ -32,7 +31,6 @@ public:
/// <summary>
/// Gets the common key.
/// </summary>
/// <returns>The key.</returns>
FORCE_INLINE TKey GetKey()
{
return _key;
@@ -52,7 +50,7 @@ public:
/// <param name="predicate">The prediction function. Should return true for the target element to find.</param>
/// <returns>The index of the element or -1 if nothing found.</returns>
template<typename T, typename AllocationType>
static int32 IndexOf(const Array<T, AllocationType>& obj, const std::function<bool(const T&)>& predicate)
static int32 IndexOf(const Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate)
{
for (int32 i = 0; i < obj.Count(); i++)
{
@@ -71,7 +69,7 @@ public:
/// <param name="predicate">The prediction function.</param>
/// <returns>True if any element in the collection matches the prediction, otherwise false.</returns>
template<typename T, typename AllocationType>
static bool Any(const Array<T, AllocationType>& obj, const std::function<bool(const T&)>& predicate)
static bool Any(const Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate)
{
for (int32 i = 0; i < obj.Count(); i++)
{
@@ -90,7 +88,7 @@ public:
/// <param name="predicate">The prediction function.</param>
/// <returns>True if all elements in the collection matches the prediction, otherwise false.</returns>
template<typename T, typename AllocationType>
static int32 All(const Array<T, AllocationType>& obj, const std::function<bool(const T&)>& predicate)
static int32 All(const Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate)
{
for (int32 i = 0; i < obj.Count(); i++)
{
@@ -109,7 +107,7 @@ public:
/// <param name="keySelector">A function to extract the key for each element.</param>
/// <param name="result">The result collection with groups.</param>
template<typename TSource, typename TKey, typename AllocationType>
static void GroupBy(const Array<TSource, AllocationType>& obj, const std::function<TKey(TSource const&)>& keySelector, Array<IGrouping<TKey, TSource>, AllocationType>& result)
static void GroupBy(const Array<TSource, AllocationType>& obj, const Function<TKey(TSource const&)>& keySelector, Array<IGrouping<TKey, TSource>, AllocationType>& result)
{
Dictionary<TKey, IGrouping<TKey, TSource>> data(static_cast<int32>(obj.Count() * 3.0f));
for (int32 i = 0; i < obj.Count(); i++)

View File

@@ -105,6 +105,14 @@ int32 Engine::Main(const Char* cmdLine)
Globals::StartupFolder = Globals::BinariesFolder = Platform::GetMainDirectory();
#if USE_EDITOR
Globals::StartupFolder /= TEXT("../../../..");
#if PLATFORM_MAC
if (Globals::BinariesFolder.EndsWith(TEXT(".app/Contents")))
{
// If running editor from application package on macOS
Globals::StartupFolder = Globals::BinariesFolder;
Globals::BinariesFolder /= TEXT("MacOS");
}
#endif
#endif
StringUtils::PathRemoveRelativeParts(Globals::StartupFolder);
FileSystem::NormalizePath(Globals::BinariesFolder);
@@ -122,7 +130,6 @@ int32 Engine::Main(const Char* cmdLine)
}
EngineImpl::InitPaths();
EngineImpl::InitLog();
#if USE_EDITOR
@@ -542,7 +549,8 @@ void EngineImpl::InitLog()
LOG(Info, "Product: {0}, Company: {1}", Globals::ProductName, Globals::CompanyName);
LOG(Info, "Current culture: {0}", Platform::GetUserLocaleName());
LOG(Info, "Command line: {0}", CommandLine);
LOG(Info, "Base directory: {0}", Globals::StartupFolder);
LOG(Info, "Base folder: {0}", Globals::StartupFolder);
LOG(Info, "Binaries folder: {0}", Globals::BinariesFolder);
LOG(Info, "Temporary folder: {0}", Globals::TemporaryFolder);
LOG(Info, "Project folder: {0}", Globals::ProjectFolder);
#if USE_EDITOR

View File

@@ -1,5 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
namespace FlaxEngine
{
/// <summary>
@@ -23,11 +25,17 @@ namespace FlaxEngine
/// </summary>
public float ValueRaw => Input.GetAxisRaw(Name);
/// <summary>
/// Occurs when axis is changed. Called before scripts update.
/// </summary>
public event Action ValueChanged;
/// <summary>
/// Initializes a new instance of the <see cref="InputAxis"/> class.
/// </summary>
public InputAxis()
{
Input.AxisValueChanged += Handler;
}
/// <summary>
@@ -36,7 +44,31 @@ namespace FlaxEngine
/// <param name="name">The axis name.</param>
public InputAxis(string name)
{
Input.AxisValueChanged += Handler;
Name = name;
}
private void Handler(string name)
{
if (string.Equals(Name, name, StringComparison.OrdinalIgnoreCase))
ValueChanged?.Invoke();
}
/// <summary>
/// Finalizes an instance of the <see cref="InputAxis"/> class.
/// </summary>
~InputAxis()
{
Input.AxisValueChanged -= Handler;
}
/// <summary>
/// Releases this object.
/// </summary>
public void Dispose()
{
Input.AxisValueChanged -= Handler;
GC.SuppressFinalize(this);
}
}
}

View File

@@ -16,15 +16,36 @@ namespace FlaxEngine
public string Name;
/// <summary>
/// Returns true if the event has been triggered during the current frame (e.g. user pressed a key). Use <see cref="Triggered"/> to catch events without active waiting.
/// Returns true if the event has been triggered during the current frame (e.g. user pressed a key). Use <see cref="Pressed"/> to catch events without active waiting.
/// </summary>
public bool Active => Input.GetAction(Name);
/// <summary>
/// Returns the event state. Use Use <see cref="Pressed"/>, <see cref="Pressing"/>, <see cref="Released"/> to catch events without active waiting.
/// </summary>
public InputActionState State => Input.GetActionState(Name);
/// <summary>
/// Occurs when event is triggered (e.g. user pressed a key). Called before scripts update.
/// </summary>
[System.Obsolete("Depreciated in 1.7, use Pressed Action.")]
public event Action Triggered;
/// <summary>
/// Occurs when event is pressed (e.g. user pressed a key). Called before scripts update.
/// </summary>
public event Action Pressed;
/// <summary>
/// Occurs when event is being pressing (e.g. user pressing a key). Called before scripts update.
/// </summary>
public event Action Pressing;
/// <summary>
/// Occurs when event is released (e.g. user releases a key). Called before scripts update.
/// </summary>
public event Action Released;
/// <summary>
/// Initializes a new instance of the <see cref="InputEvent"/> class.
/// </summary>
@@ -51,10 +72,26 @@ namespace FlaxEngine
Input.ActionTriggered -= Handler;
}
private void Handler(string name)
private void Handler(string name, InputActionState state)
{
if (string.Equals(name, Name, StringComparison.OrdinalIgnoreCase))
if (!string.Equals(name, Name, StringComparison.OrdinalIgnoreCase))
return;
switch (state)
{
case InputActionState.None: break;
case InputActionState.Waiting: break;
case InputActionState.Pressing:
Pressing?.Invoke();
break;
case InputActionState.Press:
Triggered?.Invoke();
Pressed?.Invoke();
break;
case InputActionState.Release:
Released?.Invoke();
break;
default: break;
}
}
/// <summary>

View File

@@ -130,7 +130,7 @@ void FoliageType::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE(Model);
const std::function<bool(const ModelInstanceEntry&)> IsValidMaterial = [](const ModelInstanceEntry& e) -> bool
const Function<bool(const ModelInstanceEntry&)> IsValidMaterial = [](const ModelInstanceEntry& e) -> bool
{
return e.Material;
};

View File

@@ -66,7 +66,7 @@ public:
const auto parentModelIndex = node.ParentIndex;
// Find matching node in skeleton (or map to best parent)
const std::function<bool(const T&)> f = [node](const T& x) -> bool
const Function<bool(const T&)> f = [node](const T& x) -> bool
{
return x.Name == node.Name;
};

View File

@@ -459,9 +459,9 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des
{
case VK_DESCRIPTOR_TYPE_SAMPLER:
{
const VkSampler sampler = _samplerHandles[slot];
ASSERT(sampler);
needsWrite |= dsWriter.WriteSampler(descriptorIndex, sampler, index);
const VkSampler handle = _samplerHandles[slot];
ASSERT(handle);
needsWrite |= dsWriter.WriteSampler(descriptorIndex, handle, index);
break;
}
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
@@ -547,12 +547,18 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des
}
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
{
auto cb = handles[slot];
ASSERT(cb);
VkBuffer buffer;
VkDeviceSize offset, range;
uint32 dynamicOffset;
cb->DescriptorAsDynamicUniformBuffer(this, buffer, offset, range, dynamicOffset);
auto handle = handles[slot];
VkBuffer buffer = VK_NULL_HANDLE;
VkDeviceSize offset = 0, range = 0;
uint32 dynamicOffset = 0;
if (handle)
handle->DescriptorAsDynamicUniformBuffer(this, buffer, offset, range, dynamicOffset);
else
{
const auto dummy = _device->HelperResources.GetDummyBuffer();
buffer = dummy->GetHandle();
range = dummy->GetSize();
}
needsWrite |= dsWriter.WriteDynamicUniformBuffer(descriptorIndex, buffer, offset, range, dynamicOffset, index);
break;
}

View File

@@ -35,6 +35,10 @@ static const char* GValidationLayers[] =
static const char* GInstanceExtensions[] =
{
#if PLATFORM_APPLE_FAMILY && defined(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)
VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
#endif
#if VK_EXT_validation_cache
VK_EXT_VALIDATION_CACHE_EXTENSION_NAME,
#endif
@@ -46,6 +50,9 @@ static const char* GInstanceExtensions[] =
static const char* GDeviceExtensions[] =
{
#if PLATFORM_APPLE_FAMILY && defined(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
#endif
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
#if VK_KHR_maintenance1
VK_KHR_MAINTENANCE1_EXTENSION_NAME,
@@ -571,7 +578,7 @@ void GPUDeviceVulkan::ParseOptionalDeviceExtensions(const Array<const char*>& de
const auto HasExtension = [&deviceExtensions](const char* name) -> bool
{
const std::function<bool(const char* const&)> CheckCallback = [&name](const char* const& extension) -> bool
const Function<bool(const char* const&)> CheckCallback = [&name](const char* const& extension) -> bool
{
return StringUtils::Compare(extension, name) == 0;
};

View File

@@ -431,7 +431,7 @@ void DeferredDeletionQueueVulkan::EnqueueGenericResource(Type type, uint64 handl
ScopeLock lock(_locker);
#if BUILD_DEBUG
const std::function<bool(const Entry&)> ContainsHandle = [handle](const Entry& e)
const Function<bool(const Entry&)> ContainsHandle = [handle](const Entry& e)
{
return e.Handle == handle;
};
@@ -868,7 +868,7 @@ GPUBufferVulkan* HelperResourcesVulkan::GetDummyBuffer()
if (!_dummyBuffer)
{
_dummyBuffer = (GPUBufferVulkan*)_device->CreateBuffer(TEXT("DummyBuffer"));
_dummyBuffer->Init(GPUBufferDescription::Buffer(sizeof(int32), GPUBufferFlags::ShaderResource | GPUBufferFlags::UnorderedAccess, PixelFormat::R32_SInt));
_dummyBuffer->Init(GPUBufferDescription::Buffer(sizeof(int32) * 256, GPUBufferFlags::ShaderResource | GPUBufferFlags::UnorderedAccess, PixelFormat::R32_SInt));
}
return _dummyBuffer;
@@ -1078,13 +1078,16 @@ GPUDevice* GPUDeviceVulkan::Create()
VkInstanceCreateInfo instInfo;
RenderToolsVulkan::ZeroStruct(instInfo, VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO);
#if PLATFORM_APPLE_FAMILY
instInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
#endif
instInfo.pApplicationInfo = &appInfo;
GetInstanceLayersAndExtensions(InstanceExtensions, InstanceLayers, SupportsDebugUtilsExt);
const auto hasExtension = [](const Array<const char*>& extensions, const char* name) -> bool
{
const std::function<bool(const char* const&)> callback = [&name](const char* const& extension) -> bool
const Function<bool(const char* const&)> callback = [&name](const char* const& extension) -> bool
{
return extension && StringUtils::Compare(extension, name) == 0;
};

View File

@@ -51,7 +51,8 @@ public sealed class VulkanSdk : Sdk
var subDirs = Directory.GetDirectories(path);
if (subDirs.Length != 0)
{
path = Path.Combine(subDirs[0], "macOS");
Flax.Build.Utilities.SortVersionDirectories(subDirs);
path = Path.Combine(subDirs.Last(), "macOS");
if (Directory.Exists(path))
vulkanSdk = path;
}

View File

@@ -41,4 +41,15 @@
#define VMA_NOT_NULL
#include <ThirdParty/VulkanMemoryAllocator/vk_mem_alloc.h>
#if PLATFORM_APPLE_FAMILY
// Declare potentially missing extensions from newer SDKs
#ifndef VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME
#define VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME "VK_KHR_portability_enumeration"
#define VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR 0x00000001
#endif
#ifndef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME
#define VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME "VK_KHR_portability_subset"
#endif
#endif
#endif

View File

@@ -97,7 +97,8 @@ Action Input::MouseLeave;
Delegate<const Float2&, int32> Input::TouchDown;
Delegate<const Float2&, int32> Input::TouchMove;
Delegate<const Float2&, int32> Input::TouchUp;
Delegate<StringView> Input::ActionTriggered;
Delegate<StringView, InputActionState> Input::ActionTriggered;
Delegate<StringView> Input::AxisValueChanged;
Array<ActionConfig> Input::ActionMappings;
Array<AxisConfig> Input::AxisMappings;
@@ -1017,14 +1018,22 @@ void InputService::Update()
Input::SetMousePosition(Screen::GetSize() * 0.5f);
}
// Send events for the active actions (send events only in play mode)
// Send events for the active actions and axes (send events only in play mode)
if (!Time::GetGamePaused())
{
for (auto i = Axes.Begin(); i.IsNotEnd(); ++i)
{
if (Math::NotNearEqual(i->Value.Value, i->Value.PrevKeyValue))
{
Input::AxisValueChanged(i->Key);
}
}
for (auto i = Actions.Begin(); i.IsNotEnd(); ++i)
{
if (i->Value.Active)
if (i->Value.State != InputActionState::Waiting)
{
Input::ActionTriggered(i->Key);
Input::ActionTriggered(i->Key, i->Value.State);
}
}
}

View File

@@ -293,7 +293,13 @@ public:
/// Event fired when virtual input action is triggered. Called before scripts update. See <see cref="ActionMappings"/> to edit configuration.
/// </summary>
/// <seealso cref="InputEvent"/>
API_EVENT() static Delegate<StringView> ActionTriggered;
API_EVENT() static Delegate<StringView, InputActionState> ActionTriggered;
/// <summary>
/// Event fired when virtual input axis is changed. Called before scripts update. See <see cref="AxisMappings"/> to edit configuration.
/// </summary>
/// <seealso cref="InputAxis"/>
API_EVENT() static Delegate<StringView> AxisValueChanged;
/// <summary>
/// Gets the value of the virtual action identified by name. Use <see cref="ActionMappings"/> to get the current config.

View File

@@ -0,0 +1,65 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
#if USE_EDITOR
#include "Engine/Core/Cache.h"
#include "Engine/Scripting/Script.h"
#include "Engine/Scripting/ScriptingObjectReference.h"
#include "Engine/Serialization/JsonWriters.h"
/// <summary>
/// Actor script component that represents missing script.
/// </summary>
API_CLASS(Attributes="HideInEditor") class FLAXENGINE_API MissingScript : public Script
{
API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE(MissingScript);
private:
ScriptingObjectReference<Script> _referenceScript;
public:
/// <summary>
/// Namespace and type name of missing script.
/// </summary>
API_FIELD(Attributes="ReadOnly") String MissingTypeName;
/// <summary>
/// Missing script serialized data.
/// </summary>
API_FIELD(Hidden, Attributes="HideInEditor") String Data;
/// <summary>
/// Field for assigning new script to transfer data to.
/// </summary>
API_PROPERTY() ScriptingObjectReference<Script> GetReferenceScript() const
{
return _referenceScript;
}
/// <summary>
/// Field for assigning new script to transfer data to.
/// </summary>
API_PROPERTY() void SetReferenceScript(const ScriptingObjectReference<Script>& value)
{
_referenceScript = value;
if (Data.IsEmpty())
return;
rapidjson_flax::Document document;
document.Parse(Data.ToStringAnsi().GetText());
auto modifier = Cache::ISerializeModifier.Get();
_referenceScript->Deserialize(document, modifier.Value);
DeleteObject();
}
};
inline MissingScript::MissingScript(const SpawnParams& params)
: Script(params)
{
}
#endif

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "SceneObjectsFactory.h"
#include "Components/MissingScript.h"
#include "Engine/Level/Actor.h"
#include "Engine/Level/Prefabs/Prefab.h"
#include "Engine/Content/Content.h"
@@ -230,7 +231,8 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::
rapidjson_flax::StringBuffer buffer;
PrettyJsonWriter writer(buffer);
value.Accept(writer.GetWriter());
LOG(Warning, "Failed to deserialize scene object from data: {0}", String(buffer.GetString()));
String bufferStr(buffer.GetString());
LOG(Warning, "Failed to deserialize scene object from data: {0}", bufferStr);
// Try to log some useful info about missing object (eg. it's parent name for faster fixing)
const auto parentIdMember = value.FindMember("ParentID");
@@ -240,6 +242,14 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::
Actor* parent = Scripting::FindObject<Actor>(parentId);
if (parent)
{
#if USE_EDITOR
// Add dummy script
auto* dummyScript = parent->AddScript<MissingScript>();
const auto parentIdMember = value.FindMember("TypeName");
if (parentIdMember != value.MemberEnd() && parentIdMember->value.IsString())
dummyScript->MissingTypeName = parentIdMember->value.GetString();
dummyScript->Data = MoveTemp(bufferStr);
#endif
LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName());
}
}

View File

@@ -30,6 +30,7 @@
#include <stdlib.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <mach/mach_time.h>
#include <mach-o/dyld.h>
#include <uuid/uuid.h>
@@ -316,6 +317,14 @@ bool ApplePlatform::Init()
OnPlatformUserAdd(New<User>(username));
}
// Increase the maximum number of simultaneously open files
{
struct rlimit limit;
limit.rlim_cur = OPEN_MAX;
limit.rlim_max = RLIM_INFINITY;
setrlimit(RLIMIT_NOFILE, &limit);
}
AutoreleasePool = [[NSAutoreleasePool alloc] init];
return false;

View File

@@ -351,6 +351,34 @@ namespace FlaxEngine.Json
}
}
/// <summary>
/// Serialize <see cref="Tag"/> as inlined text.
/// </summary>
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
internal class TagConverter : JsonConverter
{
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
{
var tag = (Tag)value;
writer.WriteValue(tag.ToString());
}
/// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
return Tags.Get((string)reader.Value);
return Tag.Default;
}
/// <inheritdoc />
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Tag);
}
}
/*
/// <summary>
/// Serialize Guid values using `N` format

View File

@@ -127,6 +127,7 @@ namespace FlaxEngine.Json
settings.Converters.Add(new MarginConverter());
settings.Converters.Add(new VersionConverter());
settings.Converters.Add(new LocalizedStringConverter());
settings.Converters.Add(new TagConverter());
//settings.Converters.Add(new GuidConverter());
return settings;
}

View File

@@ -705,8 +705,15 @@ bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, Opti
return true;
}
// Create root node
AssimpNode& rootNode = context->Nodes.AddOne();
rootNode.ParentIndex = -1;
rootNode.LodIndex = 0;
rootNode.Name = TEXT("Root");
rootNode.LocalTransform = Transform::Identity;
// Process imported scene nodes
ProcessNodes(*context, context->Scene->mRootNode, -1);
ProcessNodes(*context, context->Scene->mRootNode, 0);
}
DeleteMe<AssimpImporterData> contextCleanup(options.SplitContext ? nullptr : context);
@@ -822,7 +829,13 @@ bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, Opti
const auto animations = context->Scene->mAnimations[animIndex];
data.Animation.Channels.Resize(animations->mNumChannels, false);
data.Animation.Duration = animations->mDuration;
data.Animation.FramesPerSecond = animations->mTicksPerSecond != 0.0 ? animations->mTicksPerSecond : 25.0;
data.Animation.FramesPerSecond = animations->mTicksPerSecond;
if (data.Animation.FramesPerSecond <= 0)
{
data.Animation.FramesPerSecond = context->Options.DefaultFrameRate;
if (data.Animation.FramesPerSecond <= 0)
data.Animation.FramesPerSecond = 30.0f;
}
for (unsigned i = 0; i < animations->mNumChannels; i++)
{

View File

@@ -1474,7 +1474,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
// Group meshes that can be merged together
typedef Pair<int32, int32> MeshGroupKey;
const std::function<MeshGroupKey(MeshData* const&)> f = [](MeshData* const& x) -> MeshGroupKey
const Function<MeshGroupKey(MeshData* const&)> f = [](MeshData* const& x) -> MeshGroupKey
{
return MeshGroupKey(x->NodeIndex, x->MaterialSlotIndex);
};