Merge branch 'master' into collection-ui

This commit is contained in:
Chandler Cox
2024-01-26 09:54:03 -06:00
616 changed files with 18797 additions and 9179 deletions

View File

@@ -114,14 +114,19 @@ void Behavior::UpdateAsync()
void Behavior::StartLogic()
{
if (_result == BehaviorUpdateResult::Running)
return;
PROFILE_CPU();
// Ensure to have tree loaded on begin play
// Ensure to have tree loaded on play
CHECK(Tree && !Tree->WaitForLoaded());
BehaviorTree* tree = Tree.Get();
CHECK(tree->Graph.Root);
// Setup state
_result = BehaviorUpdateResult::Running;
_accumulatedTime = 0.0f;
_totalTime = 0;
// Init knowledge
_knowledge.InitMemory(tree);
@@ -135,6 +140,7 @@ void Behavior::StopLogic(BehaviorUpdateResult result)
_accumulatedTime = 0.0f;
_totalTime = 0;
_result = result;
_knowledge.FreeMemory();
}
void Behavior::ResetLogic()
@@ -170,7 +176,11 @@ void Behavior::OnDisable()
bool Behavior::GetNodeDebugRelevancy(const BehaviorTreeNode* node, const Behavior* behavior)
{
return node && behavior && node->_executionIndex != -1 && behavior->_knowledge.RelevantNodes.Get(node->_executionIndex);
return node &&
behavior &&
node->_executionIndex >= 0 &&
node->_executionIndex < behavior->_knowledge.RelevantNodes.Count() &&
behavior->_knowledge.RelevantNodes.Get(node->_executionIndex);
}
String Behavior::GetNodeDebugInfo(const BehaviorTreeNode* node, Behavior* behavior)
@@ -179,7 +189,7 @@ String Behavior::GetNodeDebugInfo(const BehaviorTreeNode* node, Behavior* behavi
return String::Empty;
BehaviorUpdateContext context;
Platform::MemoryClear(&context, sizeof(context));
if (behavior && node->_executionIndex != -1 && behavior->_knowledge.RelevantNodes.Get(node->_executionIndex))
if (GetNodeDebugRelevancy(node, behavior))
{
// Pass behavior and knowledge data only for relevant nodes to properly access it
context.Behavior = behavior;

View File

@@ -83,7 +83,7 @@ bool AccessVariant(Variant& instance, const StringAnsiView& member, Variant& val
}
}
#endif
else
else if (typeName.HasChars())
{
LOG(Warning, "Missing scripting type \'{0}\'", String(typeName));
}
@@ -144,13 +144,22 @@ BehaviorKnowledge::~BehaviorKnowledge()
void BehaviorKnowledge::InitMemory(BehaviorTree* tree)
{
ASSERT_LOW_LAYER(!Tree && tree);
if (Tree)
FreeMemory();
if (!tree)
return;
Tree = tree;
Blackboard = Variant::NewValue(tree->Graph.Root->BlackboardType);
RelevantNodes.Resize(tree->Graph.NodesCount, false);
RelevantNodes.SetAll(false);
if (!Memory && tree->Graph.NodesStatesSize)
{
Memory = Allocator::Allocate(tree->Graph.NodesStatesSize);
#if !BUILD_RELEASE
// Clear memory to make it easier to spot missing data issues (eg. zero GCHandle in C# BT node due to missing state init)
Platform::MemoryClear(Memory, tree->Graph.NodesStatesSize);
#endif
}
}
void BehaviorKnowledge::FreeMemory()

View File

@@ -202,7 +202,7 @@ namespace FlaxEngine
public T Get(BehaviorKnowledge knowledge)
{
if (knowledge != null && knowledge.Get(Path, out var value))
return (T)value;
return Utilities.VariantUtils.Cast<T>(value);
return default;
}
@@ -218,7 +218,7 @@ namespace FlaxEngine
object tmp = null;
bool result = knowledge != null && knowledge.Get(Path, out tmp);
if (result)
value = (T)tmp;
value = Utilities.VariantUtils.Cast<T>(tmp);
return result;
}

View File

@@ -95,12 +95,16 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T GetState<T>(IntPtr memory) where T : struct
{
var ptr = IntPtr.Add(memory, _memoryOffset).ToPointer();
var handle = GCHandle.FromIntPtr(Unsafe.Read<IntPtr>(ptr));
var ptr = Unsafe.Read<IntPtr>(IntPtr.Add(memory, _memoryOffset).ToPointer());
#if !BUILD_RELEASE
if (ptr == IntPtr.Zero)
throw new Exception($"Missing state '{typeof(T).FullName}' for node '{GetType().FullName}'");
#endif
var handle = GCHandle.FromIntPtr(ptr);
var state = handle.Target;
#if !BUILD_RELEASE
if (state == null)
throw new NullReferenceException();
throw new Exception($"Missing state '{typeof(T).FullName}' for node '{GetType().FullName}'");
#endif
return ref Unsafe.Unbox<T>(state);
}
@@ -111,8 +115,10 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FreeState(IntPtr memory)
{
var ptr = IntPtr.Add(memory, _memoryOffset).ToPointer();
var handle = GCHandle.FromIntPtr(Unsafe.Read<IntPtr>(ptr));
var ptr = Unsafe.Read<IntPtr>(IntPtr.Add(memory, _memoryOffset).ToPointer());
if (ptr == IntPtr.Zero)
return;
var handle = GCHandle.FromIntPtr(ptr);
handle.Free();
}
}

View File

@@ -85,6 +85,8 @@ BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext&
result = BehaviorUpdateResult::Failed;
else
result = Update(context);
if ((int32)result < 0 || (int32)result > (int32)BehaviorUpdateResult::Failed)
result = BehaviorUpdateResult::Failed; // Invalid value is a failure
// Post-process result from decorators
for (BehaviorTreeDecorator* decorator : _decorators)
@@ -622,8 +624,10 @@ void BehaviorTreeLoopDecorator::PostUpdate(const BehaviorUpdateContext& context,
if (result == BehaviorUpdateResult::Success)
{
auto state = GetState<State>(context.Memory);
state->Loops--;
if (state->Loops > 0)
if (!InfiniteLoop)
state->Loops--;
if (state->Loops > 0 || InfiniteLoop)
{
// Keep running in a loop but reset node's state (preserve self state)
result = BehaviorUpdateResult::Running;

View File

@@ -305,12 +305,16 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeLoopDecorator : public Behavi
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeLoopDecorator, BehaviorTreeDecorator);
API_AUTO_SERIALIZATION();
// Amount of times to execute the node. Unused if LoopCountSelector is used.
API_FIELD(Attributes="EditorOrder(10), Limit(0)")
// Is the loop infinite (until failed)?
API_FIELD(Attributes = "EditorOrder(10)")
bool InfiniteLoop = false;
// Amount of times to execute the node. Unused if LoopCountSelector is used or if InfiniteLoop is used.
API_FIELD(Attributes="EditorOrder(20), Limit(0), VisibleIf(nameof(InfiniteLoop), true)")
int32 LoopCount = 3;
// Amount of times to execute the node from behavior's knowledge (blackboard, goal or sensor). If set, overrides LoopCount.
API_FIELD(Attributes="EditorOrder(20)")
// Amount of times to execute the node from behavior's knowledge (blackboard, goal or sensor). If set, overrides LoopCount. Unused if InfiniteLoop is used.
API_FIELD(Attributes="EditorOrder(30), VisibleIf(nameof(InfiniteLoop), true)")
BehaviorKnowledgeSelector<int32> LoopCountSelector;
public:

View File

@@ -17,6 +17,11 @@ API_CLASS(Abstract) class FLAXENGINE_API AnimEvent : public SerializableScriptin
{
DECLARE_SCRIPTING_TYPE(AnimEvent);
/// <summary>
/// Indicates whether the event can be executed in async from a thread that updates the animated model. Otherwise, event execution will be delayed until the sync point of the animated model and called from the main thread. Async events need to precisely handle data access, especially when it comes to editing scene objects with multi-threading.
/// </summary>
API_FIELD(Attributes="HideInEditor, NoSerialize") bool Async = false;
#if USE_EDITOR
/// <summary>
/// Event display color in the Editor.

View File

@@ -98,7 +98,6 @@ public:
/// </summary>
struct AnimationData
{
public:
/// <summary>
/// The duration of the animation (in frames).
/// </summary>
@@ -114,6 +113,11 @@ public:
/// </summary>
bool EnableRootMotion = false;
/// <summary>
/// The animation name.
/// </summary>
String Name;
/// <summary>
/// The custom node name to be used as a root motion source. If not specified the actual root node will be used.
/// </summary>
@@ -131,14 +135,14 @@ public:
FORCE_INLINE float GetLength() const
{
#if BUILD_DEBUG
ASSERT(FramesPerSecond != 0);
ASSERT(FramesPerSecond > ZeroTolerance);
#endif
return static_cast<float>(Duration / FramesPerSecond);
}
uint64 GetMemoryUsage() const
{
uint64 result = RootNodeName.Length() * sizeof(Char) + Channels.Capacity() * sizeof(NodeAnimationData);
uint64 result = (Name.Length() + RootNodeName.Length()) * sizeof(Char) + Channels.Capacity() * sizeof(NodeAnimationData);
for (const auto& e : Channels)
result += e.GetMemoryUsage();
return result;
@@ -151,9 +155,7 @@ public:
{
int32 result = 0;
for (int32 i = 0; i < Channels.Count(); i++)
{
result += Channels[i].GetKeyframesCount();
}
return result;
}
@@ -166,6 +168,7 @@ public:
::Swap(Duration, other.Duration);
::Swap(FramesPerSecond, other.FramesPerSecond);
::Swap(EnableRootMotion, other.EnableRootMotion);
::Swap(Name, other.Name);
::Swap(RootNodeName, other.RootNodeName);
Channels.Swap(other.Channels);
}
@@ -175,6 +178,7 @@ public:
/// </summary>
void Dispose()
{
Name.Clear();
Duration = 0.0;
FramesPerSecond = 0.0;
RootNodeName.Clear();

View File

@@ -146,6 +146,7 @@ void AnimationsSystem::PostExecute(TaskGraph* graph)
auto animatedModel = AnimationManagerInstance.UpdateList[index];
if (CanUpdateModel(animatedModel))
{
animatedModel->GraphInstance.InvokeAnimEvents();
animatedModel->OnAnimationUpdated_Sync();
}
}

View File

@@ -99,10 +99,7 @@ void BlendPoseBucketInit(AnimGraphInstanceData::Bucket& bucket)
void StateMachineBucketInit(AnimGraphInstanceData::Bucket& bucket)
{
bucket.StateMachine.LastUpdateFrame = 0;
bucket.StateMachine.CurrentState = nullptr;
bucket.StateMachine.ActiveTransition = nullptr;
bucket.StateMachine.TransitionPosition = 0.0f;
Platform::MemoryClear(&bucket.StateMachine, sizeof(bucket.StateMachine));
}
void SlotBucketInit(AnimGraphInstanceData::Bucket& bucket)

View File

@@ -38,22 +38,17 @@ void AnimGraphImpulse::SetNodeModelTransformation(SkeletonData& skeleton, int32
void AnimGraphInstanceData::Clear()
{
Version = 0;
LastUpdateTime = -1;
CurrentFrame = 0;
RootTransform = Transform::Identity;
RootMotion = Transform::Identity;
ClearState();
Slots.Clear();
Parameters.Resize(0);
State.Resize(0);
NodesPose.Resize(0);
Slots.Resize(0);
for (const auto& e : Events)
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)Object, e.Anim, 0.0f, 0.0f);
Events.Resize(0);
}
void AnimGraphInstanceData::ClearState()
{
for (const auto& e : ActiveEvents)
OutgoingEvents.Add(e.End((AnimatedModel*)Object));
ActiveEvents.Clear();
InvokeAnimEvents();
Version = 0;
LastUpdateTime = -1;
CurrentFrame = 0;
@@ -61,10 +56,7 @@ void AnimGraphInstanceData::ClearState()
RootMotion = Transform::Identity;
State.Resize(0);
NodesPose.Resize(0);
Slots.Clear();
for (const auto& e : Events)
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)Object, e.Anim, 0.0f, 0.0f);
Events.Clear();
TraceEvents.Clear();
}
void AnimGraphInstanceData::Invalidate()
@@ -73,6 +65,43 @@ void AnimGraphInstanceData::Invalidate()
CurrentFrame = 0;
}
void AnimGraphInstanceData::InvokeAnimEvents()
{
const bool all = IsInMainThread();
for (int32 i = 0; i < OutgoingEvents.Count(); i++)
{
const OutgoingEvent e = OutgoingEvents[i];
if (all || e.Instance->Async)
{
OutgoingEvents.RemoveAtKeepOrder(i);
switch (e.Type)
{
case OutgoingEvent::OnEvent:
e.Instance->OnEvent(e.Actor, e.Anim, e.Time, e.DeltaTime);
break;
case OutgoingEvent::OnBegin:
((AnimContinuousEvent*)e.Instance)->OnBegin(e.Actor, e.Anim, e.Time, e.DeltaTime);
break;
case OutgoingEvent::OnEnd:
((AnimContinuousEvent*)e.Instance)->OnEnd(e.Actor, e.Anim, e.Time, e.DeltaTime);
break;
}
}
}
}
AnimGraphInstanceData::OutgoingEvent AnimGraphInstanceData::ActiveEvent::End(AnimatedModel* actor) const
{
OutgoingEvent out;
out.Instance = Instance;
out.Actor = actor;
out.Anim = Anim;
out.Time = 0.0f;
out.DeltaTime = 0.0f;
out.Type = OutgoingEvent::OnEnd;
return out;
}
AnimGraphImpulse* AnimGraphNode::GetNodes(AnimGraphExecutor* executor)
{
auto& context = AnimGraphExecutor::Context.Get();
@@ -208,8 +237,9 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
// Initialize buckets
ResetBuckets(context, &_graph);
}
for (auto& e : data.Events)
for (auto& e : data.ActiveEvents)
e.Hit = false;
data.TraceEvents.Clear();
// Init empty nodes data
context.EmptyNodes.RootMotion = Transform::Identity;
@@ -240,19 +270,34 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
if (animResult == nullptr)
animResult = GetEmptyNodes();
}
if (data.Events.Count() != 0)
if (data.ActiveEvents.Count() != 0)
{
ANIM_GRAPH_PROFILE_EVENT("Events");
for (int32 i = data.Events.Count() - 1; i >= 0; i--)
for (int32 i = data.ActiveEvents.Count() - 1; i >= 0; i--)
{
const auto& e = data.Events[i];
const auto& e = data.ActiveEvents[i];
if (!e.Hit)
{
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)context.Data->Object, e.Anim, 0.0f, 0.0f);
data.Events.RemoveAt(i);
// Remove active event that was not hit during this frame (eg. animation using it was not used in blending)
data.OutgoingEvents.Add(e.End((AnimatedModel*)context.Data->Object));
data.ActiveEvents.RemoveAt(i);
}
}
}
#if !BUILD_RELEASE
{
// Perform sanity check on nodes pose to prevent crashes due to NaNs
bool anyInvalid = animResult->RootMotion.IsNanOrInfinity();
for (int32 i = 0; i < animResult->Nodes.Count(); i++)
anyInvalid |= animResult->Nodes.Get()[i].IsNanOrInfinity();
if (anyInvalid)
{
LOG(Error, "Animated Model pose contains NaNs due to animations sampling/blending bug.");
context.Data = nullptr;
return;
}
}
#endif
SkeletonData* animResultSkeleton = &skeleton;
// Retarget animation when using output pose from other skeleton
@@ -284,7 +329,6 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
RetargetSkeletonNode(sourceSkeleton, targetSkeleton, mapping, node, i);
targetNodes[i] = node;
}
}
}
@@ -319,6 +363,9 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
data.RootMotion = animResult->RootMotion;
}
// Invoke any async anim events
context.Data->InvokeAnimEvents();
// Cleanup
context.Data = nullptr;
}

View File

@@ -23,6 +23,9 @@ class AnimSubGraph;
class AnimGraphBase;
class AnimGraphNode;
class AnimGraphExecutor;
class AnimatedModel;
class AnimEvent;
class AnimContinuousEvent;
class SkinnedModel;
class SkeletonData;
@@ -126,6 +129,8 @@ public:
UseDefaultRule = 4,
InterruptionRuleRechecking = 8,
InterruptionInstant = 16,
InterruptionSourceState = 32,
InterruptionDestinationState = 64,
};
public:
@@ -194,6 +199,22 @@ struct FLAXENGINE_API AnimGraphSlot
float BlendOutTime = 0.0f;
int32 LoopCount = 0;
bool Pause = false;
bool Reset = false;
};
/// <summary>
/// The animation graph state container for a single node playback trace (eg. animation sample info or state transition). Can be used by Anim Graph debugging or custom scripting.
/// </summary>
API_STRUCT() struct FLAXENGINE_API AnimGraphTraceEvent
{
DECLARE_SCRIPTING_TYPE_MINIMAL(AnimGraphTraceEvent);
// Contextual asset used. For example, sampled animation.
API_FIELD() Asset* Asset = nullptr;
// Generic value contextual to playback type (eg. animation sample position).
API_FIELD() float Value = 0;
// Identifier of the node in the graph.
API_FIELD() uint32 NodeId = 0;
};
/// <summary>
@@ -237,7 +258,10 @@ public:
uint64 LastUpdateFrame;
AnimGraphNode* CurrentState;
AnimGraphStateTransition* ActiveTransition;
AnimGraphStateTransition* BaseTransition;
AnimGraphNode* BaseTransitionState;
float TransitionPosition;
float BaseTransitionPosition;
};
struct SlotBucket
@@ -349,16 +373,46 @@ public:
/// </summary>
void Invalidate();
/// <summary>
/// Invokes any outgoing AnimEvent and AnimContinuousEvent collected during the last animation update. When called from non-main thread only Async events will be invoked.
/// </summary>
void InvokeAnimEvents();
public:
// Anim Graph logic tracing feature that allows to collect insights of animations sampling and skeleton poses operations.
bool EnableTracing = false;
// Trace events collected when using EnableTracing option.
Array<AnimGraphTraceEvent> TraceEvents;
private:
struct Event
struct OutgoingEvent
{
enum Types
{
OnEvent,
OnBegin,
OnEnd,
};
AnimEvent* Instance;
AnimatedModel* Actor;
Animation* Anim;
float Time, DeltaTime;
Types Type;
};
struct ActiveEvent
{
AnimContinuousEvent* Instance;
Animation* Anim;
AnimGraphNode* Node;
bool Hit;
OutgoingEvent End(AnimatedModel* actor) const;
};
Array<Event, InlinedAllocation<8>> Events;
Array<ActiveEvent, InlinedAllocation<8>> ActiveEvents;
Array<OutgoingEvent, InlinedAllocation<8>> OutgoingEvents;
};
/// <summary>
@@ -441,7 +495,7 @@ public:
/// The invalid transition valid used in Transitions to indicate invalid transition linkage.
/// </summary>
const static uint16 InvalidTransitionIndex = MAX_uint16;
/// <summary>
/// The outgoing transitions from this state to the other states. Each array item contains index of the transition data from the state node graph transitions cache. Value InvalidTransitionIndex is used for last transition to indicate the transitions amount.
/// </summary>
@@ -809,7 +863,7 @@ public:
}
/// <summary>
/// Resets all the state bucket used by the given graph including sub-graphs (total). Can eb used to reset the animation state of the nested graph (including children).
/// Resets all the state bucket used by the given graph including sub-graphs (total). Can be used to reset the animation state of the nested graph (including children).
/// </summary>
void ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph);
@@ -838,5 +892,7 @@ private:
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC);
Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode);
Variant SampleState(AnimGraphNode* state);
void InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition = nullptr);
AnimGraphStateTransition* UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState = nullptr);
void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData);
};

View File

@@ -100,48 +100,50 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float
if (!k.Value.Instance)
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)
{
int32 stateIndex = -1;
if (duration > 1)
{
// Begin for continuous event
for (stateIndex = 0; stateIndex < context.Data->Events.Count(); stateIndex++)
for (stateIndex = 0; stateIndex < context.Data->ActiveEvents.Count(); stateIndex++)
{
const auto& e = context.Data->Events[stateIndex];
const auto& e = context.Data->ActiveEvents[stateIndex];
if (e.Instance == k.Value.Instance && e.Node == node)
break;
}
if (stateIndex == context.Data->Events.Count())
if (stateIndex == context.Data->ActiveEvents.Count())
{
auto& e = context.Data->Events.AddOne();
e.Instance = k.Value.Instance;
ASSERT(k.Value.Instance->Is<AnimContinuousEvent>());
auto& e = context.Data->ActiveEvents.AddOne();
e.Instance = (AnimContinuousEvent*)k.Value.Instance;
e.Anim = anim;
e.Node = node;
ASSERT(k.Value.Instance->Is<AnimContinuousEvent>());
((AnimContinuousEvent*)k.Value.Instance)->OnBegin((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
ADD_OUTGOING_EVENT(OnBegin);
}
}
// Event
k.Value.Instance->OnEvent((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
ADD_OUTGOING_EVENT(OnEvent);
if (stateIndex != -1)
context.Data->Events[stateIndex].Hit = true;
context.Data->ActiveEvents[stateIndex].Hit = true;
}
else if (duration > 1)
{
// End for continuous event
for (int32 i = 0; i < context.Data->Events.Count(); i++)
for (int32 i = 0; i < context.Data->ActiveEvents.Count(); i++)
{
const auto& e = context.Data->Events[i];
const auto& e = context.Data->ActiveEvents[i];
if (e.Instance == k.Value.Instance && e.Node == node)
{
((AnimContinuousEvent*)k.Value.Instance)->OnEnd((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
context.Data->Events.RemoveAt(i);
ADD_OUTGOING_EVENT(OnEnd);
context.Data->ActiveEvents.RemoveAt(i);
break;
}
}
}
#undef ADD_OUTGOING_EVENT
}
}
}
@@ -217,6 +219,16 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
const float animPos = GetAnimSamplePos(length, anim, pos, speed);
const float animPrevPos = GetAnimSamplePos(length, anim, prevPos, speed);
// Add to trace
auto& context = Context.Get();
if (context.Data->EnableTracing)
{
auto& trace = context.Data->TraceEvents.AddOne();
trace.Asset = anim;
trace.Value = animPos;
trace.NodeId = node->ID;
}
// Evaluate nested animations
bool hasNested = false;
if (anim->NestedAnims.Count() != 0)
@@ -458,10 +470,12 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const
{
ANIM_GRAPH_PROFILE_EVENT("Blend Pose");
if (isnan(alpha) || isinf(alpha))
alpha = 0;
alpha = Math::Saturate(alpha);
alpha = AlphaBlend::Process(alpha, alphaMode);
const auto nodes = node->GetNodes(this);
auto nodesA = static_cast<AnimGraphImpulse*>(poseA.AsPointer);
auto nodesB = static_cast<AnimGraphImpulse*>(poseB.AsPointer);
if (!ANIM_GRAPH_IS_VALID_PTR(poseA))
@@ -482,32 +496,40 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const
Variant AnimGraphExecutor::SampleState(AnimGraphNode* state)
{
// Prepare
auto& data = state->Data.State;
if (data.Graph == nullptr || data.Graph->GetRootNode() == nullptr)
{
// Invalid state graph
return Value::Null;
}
ANIM_GRAPH_PROFILE_EVENT("Evaluate State");
// Evaluate state
auto rootNode = data.Graph->GetRootNode();
auto result = eatBox((Node*)rootNode, &rootNode->Boxes[0]);
return result;
return eatBox((Node*)rootNode, &rootNode->Boxes[0]);
}
void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData)
void AnimGraphExecutor::InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition)
{
// Reset transiton
stateMachineBucket.ActiveTransition = transition;
stateMachineBucket.TransitionPosition = 0.0f;
// End base transition
if (stateMachineBucket.BaseTransition)
{
ResetBuckets(context, stateMachineBucket.BaseTransitionState->Data.State.Graph);
stateMachineBucket.BaseTransition = nullptr;
stateMachineBucket.BaseTransitionState = nullptr;
stateMachineBucket.BaseTransitionPosition = 0.0f;
}
}
AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState)
{
int32 transitionIndex = 0;
const AnimGraphNode::StateBaseData& stateData = state->Data.State;
while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
{
const uint16 idx = stateData.Transitions[transitionIndex];
ASSERT(idx < stateMachineData.Graph->StateTransitions.Count());
auto& transition = stateMachineData.Graph->StateTransitions[idx];
if (transition.Destination == stateMachineBucket.CurrentState)
if (transition.Destination == state || transition.Destination == ignoreState)
{
// Ignore transition to the current state
transitionIndex++;
@@ -515,7 +537,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
}
// Evaluate source state transition data (position, length, etc.)
const Value sourceStatePtr = SampleState(stateMachineBucket.CurrentState);
const Value sourceStatePtr = SampleState(state);
auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule?
if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr))
{
@@ -536,6 +558,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
if (transition.RuleGraph && !useDefaultRule)
{
// Execute transition rule
ANIM_GRAPH_PROFILE_EVENT("Rule");
auto rootNode = transition.RuleGraph->GetRootNode();
ASSERT(rootNode);
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
@@ -558,10 +581,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
canEnter = true;
if (canEnter)
{
// Start transition
stateMachineBucket.ActiveTransition = &transition;
stateMachineBucket.TransitionPosition = 0.0f;
break;
return &transition;
}
// Skip after Solo transition
@@ -571,6 +591,18 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
transitionIndex++;
}
// No transition
return nullptr;
}
void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData)
{
AnimGraphStateTransition* transition = UpdateStateTransitions(context, stateMachineData, stateMachineBucket.CurrentState);
if (transition)
{
InitStateTransition(context, stateMachineBucket, transition);
}
}
void ComputeMultiBlendLength(float& length, AnimGraphNode* node)
@@ -1068,20 +1100,26 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
else
{
const auto nodes = node->GetNodes(this);
const auto nodesA = static_cast<AnimGraphImpulse*>(valueA.AsPointer);
const auto nodesB = static_cast<AnimGraphImpulse*>(valueB.AsPointer);
Transform t, tA, tB;
const auto basePoseNodes = static_cast<AnimGraphImpulse*>(valueA.AsPointer);
const auto blendPoseNodes = static_cast<AnimGraphImpulse*>(valueB.AsPointer);
const auto& refrenceNodes = _graph.BaseModel.Get()->GetNodes();
Transform t, basePoseTransform, blendPoseTransform, refrenceTransform;
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
tA = nodesA->Nodes[i];
tB = nodesB->Nodes[i];
t.Translation = tA.Translation + tB.Translation;
t.Orientation = tA.Orientation * tB.Orientation;
t.Scale = tA.Scale * tB.Scale;
t.Orientation.Normalize();
Transform::Lerp(tA, t, alpha, nodes->Nodes[i]);
basePoseTransform = basePoseNodes->Nodes[i];
blendPoseTransform = blendPoseNodes->Nodes[i];
refrenceTransform = refrenceNodes[i].LocalTransform;
// base + (blend - refrence) = transform
t.Translation = basePoseTransform.Translation + (blendPoseTransform.Translation - refrenceTransform.Translation);
auto diff = Quaternion::Invert(refrenceTransform.Orientation) * blendPoseTransform.Orientation;
t.Orientation = basePoseTransform.Orientation * diff;
t.Scale = basePoseTransform.Scale + (blendPoseTransform.Scale - refrenceTransform.Scale);
//lerp base and transform
Transform::Lerp(basePoseTransform, t, alpha, nodes->Nodes[i]);
}
Transform::Lerp(nodesA->RootMotion, nodesA->RootMotion + nodesB->RootMotion, alpha, nodes->RootMotion);
Transform::Lerp(basePoseNodes->RootMotion, basePoseNodes->RootMotion + blendPoseNodes->RootMotion, alpha, nodes->RootMotion);
value = nodes;
}
}
@@ -1342,7 +1380,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
{
const bool xAxis = Math::IsZero(v0.X) && Math::IsZero(v1.X);
const bool yAxis = Math::IsZero(v0.Y) && Math::IsZero(v1.Y);
if (xAxis || yAxis)
if (xAxis && yAxis)
{
// Single animation
value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W);
}
else if (xAxis || yAxis)
{
if (yAxis)
{
@@ -1354,33 +1397,29 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
}
// Use 1D blend if points are on the same line (degenerated triangle)
// TODO: simplify this code
struct BlendData
{
float AlphaX, AlphaY;
Animation* AnimA, *AnimB;
const Float4* AnimAd, *AnimBd;
};
BlendData blendData;
if (v1.Y >= v0.Y)
{
if (p.Y < v0.Y && v1.Y >= v0.Y)
{
const float alpha = p.Y / v0.Y;
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, bAnim, aData.W, bData.W, alpha);
}
blendData = { p.Y, v0.Y, aAnim, bAnim, &aData, &bData };
else
{
const float alpha = (p.Y - v0.Y) / (v1.Y - v0.Y);
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, bAnim, cAnim, bData.W, cData.W, alpha);
}
blendData = { p.Y - v0.Y, v1.Y - v0.Y, bAnim, cAnim, &bData, &cData };
}
else
{
if (p.Y < v1.Y)
{
const float alpha = p.Y / v1.Y;
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, cAnim, aData.W, cData.W, alpha);
}
blendData = { p.Y, v1.Y, aAnim, cAnim, &aData, &cData };
else
{
const float alpha = (p.Y - v1.Y) / (v0.Y - v1.Y);
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, cAnim, bAnim, cData.W, bData.W, alpha);
}
blendData = { p.Y - v1.Y, v0.Y - v1.Y, cAnim, bAnim, &cData, &bData };
}
const float alpha = Math::IsZero(blendData.AlphaY) ? 0.0f : blendData.AlphaX / blendData.AlphaY;
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, blendData.AnimA, blendData.AnimB, blendData.AnimAd->W, blendData.AnimBd->W, alpha);
}
else
{
@@ -1492,10 +1531,9 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Blend two animations
{
const float alpha = Math::Saturate(bucket.TransitionPosition / blendDuration);
const float alpha = bucket.TransitionPosition / blendDuration;
const auto valueA = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.PreviousBlendPoseIndex), Value::Null);
const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null);
value = Blend(node, valueA, valueB, alpha, mode);
}
@@ -1601,22 +1639,21 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Enter to the first state pointed by the Entry node (without transitions)
bucket.CurrentState = data.Graph->GetRootNode();
bucket.ActiveTransition = nullptr;
bucket.TransitionPosition = 0.0f;
InitStateTransition(context, bucket);
// Reset all state buckets pof the graphs and nodes included inside the state machine
// Reset all state buckets of the graphs and nodes included inside the state machine
ResetBuckets(context, data.Graph);
}
#define END_TRANSITION() \
ResetBuckets(context, bucket.CurrentState->Data.State.Graph); \
bucket.CurrentState = bucket.ActiveTransition->Destination; \
bucket.ActiveTransition = nullptr; \
bucket.TransitionPosition = 0.0f
InitStateTransition(context, bucket)
// Update the active transition
if (bucket.ActiveTransition)
{
bucket.TransitionPosition += context.DeltaTime;
ASSERT(bucket.CurrentState);
// Check for transition end
if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration)
@@ -1624,38 +1661,70 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
END_TRANSITION();
}
// Check for transition interruption
else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking))
else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking) &&
EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule) &&
bucket.ActiveTransition->RuleGraph)
{
const bool useDefaultRule = EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
if (bucket.ActiveTransition->RuleGraph && !useDefaultRule)
// Execute transition rule
auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode();
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
{
// Execute transition rule
auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode();
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
bool cancelTransition = false;
if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
{
bool cancelTransition = false;
if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
cancelTransition = true;
}
else
{
// Blend back to the source state (remove currently applied delta and rewind transition)
bucket.TransitionPosition -= context.DeltaTime;
bucket.TransitionPosition -= context.DeltaTime;
if (bucket.TransitionPosition <= ZeroTolerance)
{
cancelTransition = true;
}
else
{
// Blend back to the source state (remove currently applied delta and rewind transition)
bucket.TransitionPosition -= context.DeltaTime;
bucket.TransitionPosition -= context.DeltaTime;
if (bucket.TransitionPosition <= ZeroTolerance)
{
cancelTransition = true;
}
}
if (cancelTransition)
{
// Go back to the source state
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
bucket.ActiveTransition = nullptr;
bucket.TransitionPosition = 0.0f;
}
}
if (cancelTransition)
{
// Go back to the source state
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
InitStateTransition(context, bucket);
}
}
}
if (bucket.ActiveTransition && !bucket.BaseTransition && EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionSourceState))
{
// Try to interrupt with any other transition in the source state (except the current transition)
if (AnimGraphStateTransition* transition = UpdateStateTransitions(context, data, bucket.CurrentState, bucket.ActiveTransition->Destination))
{
// Change active transition to the interrupted one
if (EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
{
// Cache the current blending state to be used as a base when blending towards new destination state (seamless blending after interruption)
bucket.BaseTransition = bucket.ActiveTransition;
bucket.BaseTransitionState = bucket.CurrentState;
bucket.BaseTransitionPosition = bucket.TransitionPosition;
}
bucket.ActiveTransition = transition;
bucket.TransitionPosition = 0.0f;
}
}
if (bucket.ActiveTransition && !bucket.BaseTransition && EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionDestinationState))
{
// Try to interrupt with any other transition in the destination state (except the transition back to the current state if exists)
if (AnimGraphStateTransition* transition = UpdateStateTransitions(context, data, bucket.ActiveTransition->Destination, bucket.CurrentState))
{
// Change active transition to the interrupted one
if (EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
{
// Cache the current blending state to be used as a base when blending towards new destination state (seamless blending after interruption)
bucket.BaseTransition = bucket.ActiveTransition;
bucket.BaseTransitionState = bucket.CurrentState;
bucket.BaseTransitionPosition = bucket.TransitionPosition;
}
bucket.CurrentState = bucket.ActiveTransition->Destination;
bucket.ActiveTransition = transition;
bucket.TransitionPosition = 0.0f;
}
}
}
@@ -1684,9 +1753,23 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
}
}
// Sample the current state
const auto currentState = SampleState(bucket.CurrentState);
value = currentState;
if (bucket.BaseTransitionState)
{
// Sample the other state (eg. when blending from interrupted state to the another state from the old destination)
value = SampleState(bucket.BaseTransitionState);
if (bucket.BaseTransition)
{
// Evaluate the base pose from the time when transition was interrupted
const auto destinationState = SampleState(bucket.BaseTransition->Destination);
const float alpha = bucket.BaseTransitionPosition / bucket.BaseTransition->BlendDuration;
value = Blend(node, value, destinationState, alpha, bucket.BaseTransition->BlendMode);
}
}
else
{
// Sample the current state
value = SampleState(bucket.CurrentState);
}
// Handle active transition blending
if (bucket.ActiveTransition)
@@ -1695,14 +1778,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
const auto destinationState = SampleState(bucket.ActiveTransition->Destination);
// Perform blending
const float alpha = Math::Saturate(bucket.TransitionPosition / bucket.ActiveTransition->BlendDuration);
value = Blend(node, currentState, destinationState, alpha, bucket.ActiveTransition->BlendMode);
const float alpha = bucket.TransitionPosition / bucket.ActiveTransition->BlendDuration;
value = Blend(node, value, destinationState, alpha, bucket.ActiveTransition->BlendMode);
}
// Update bucket
bucket.LastUpdateFrame = context.CurrentFrameIndex;
#undef END_TRANSITION
break;
}
// Entry
@@ -2089,6 +2170,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
auto& slot = slots[bucket.Index];
Animation* anim = slot.Animation;
ASSERT(slot.Animation && slot.Animation->IsLoaded());
if (slot.Reset)
{
// Start from the begining
slot.Reset = false;
bucket.TimePosition = 0.0f;
}
const float deltaTime = slot.Pause ? 0.0f : context.DeltaTime * slot.Speed;
const float length = anim->GetLength();
const bool loop = bucket.LoopsLeft != 0;
@@ -2109,14 +2196,15 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
bucket.LoopsLeft--;
bucket.LoopsDone++;
}
value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, slot.Speed);
// Speed is accounted for in the new time pos, so keep sample speed at 1
value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, 1);
bucket.TimePosition = newTimePos;
if (bucket.LoopsLeft == 0 && slot.BlendOutTime > 0.0f && length - slot.BlendOutTime < bucket.TimePosition)
{
// Blend out
auto input = tryGetValue(node->GetBox(1), Value::Null);
bucket.BlendOutPosition += deltaTime;
const float alpha = Math::Saturate(bucket.BlendOutPosition / slot.BlendOutTime);
const float alpha = bucket.BlendOutPosition / slot.BlendOutTime;
value = Blend(node, value, input, alpha, AlphaBlendMode::HermiteCubic);
}
else if (bucket.LoopsDone == 0 && slot.BlendInTime > 0.0f && bucket.BlendInPosition < slot.BlendInTime)
@@ -2124,7 +2212,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Blend in
auto input = tryGetValue(node->GetBox(1), Value::Null);
bucket.BlendInPosition += deltaTime;
const float alpha = Math::Saturate(bucket.BlendInPosition / slot.BlendInTime);
const float alpha = bucket.BlendInPosition / slot.BlendInTime;
value = Blend(node, input, value, alpha, AlphaBlendMode::HermiteCubic);
}
break;

View File

@@ -225,6 +225,7 @@ bool AudioClip::ExtractDataRaw(Array<byte>& resultData, AudioDataInfo& resultDat
void AudioClip::CancelStreaming()
{
Asset::CancelStreaming();
CancelStreamingTasks();
}

View File

@@ -33,7 +33,8 @@
int alError = alGetError(); \
if (alError != 0) \
{ \
LOG(Error, "OpenAL method {0} failed with error 0x{1:X} (at line {2})", TEXT(#method), alError, __LINE__ - 1); \
const Char* errorStr = GetOpenALErrorString(alError); \
LOG(Error, "OpenAL method {0} failed with error 0x{1:X}:{2} (at line {3})", TEXT(#method), alError, errorStr, __LINE__ - 1); \
} \
}
#endif
@@ -290,6 +291,28 @@ ALenum GetOpenALBufferFormat(uint32 numChannels, uint32 bitDepth)
return 0;
}
const Char* GetOpenALErrorString(int error)
{
switch (error)
{
case AL_NO_ERROR:
return TEXT("AL_NO_ERROR");
case AL_INVALID_NAME:
return TEXT("AL_INVALID_NAME");
case AL_INVALID_ENUM:
return TEXT("AL_INVALID_ENUM");
case AL_INVALID_VALUE:
return TEXT("AL_INVALID_VALUE");
case AL_INVALID_OPERATION:
return TEXT("AL_INVALID_OPERATION");
case AL_OUT_OF_MEMORY:
return TEXT("AL_OUT_OF_MEMORY");
default:
break;
}
return TEXT("???");
}
void AudioBackendOAL::Listener_OnAdd(AudioListener* listener)
{
#if ALC_MULTIPLE_LISTENERS
@@ -838,7 +861,11 @@ bool AudioBackendOAL::Base_Init()
// Init
Base_SetDopplerFactor(AudioSettings::Get()->DopplerFactor);
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // Default attenuation model
ALC::RebuildContexts(true);
int32 clampedIndex = Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1);
if (clampedIndex == Audio::GetActiveDeviceIndex())
{
ALC::RebuildContexts(true);
}
Audio::SetActiveDeviceIndex(activeDeviceIndex);
#ifdef AL_SOFT_source_spatialize
if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize"))

View File

@@ -16,11 +16,13 @@
AssetReferenceBase::~AssetReferenceBase()
{
if (_asset)
Asset* asset = _asset;
if (asset)
{
_asset->OnLoaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnLoaded>(this);
_asset->OnUnloaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnUnloaded>(this);
_asset->RemoveReference();
_asset = nullptr;
asset->OnLoaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnLoaded>(this);
asset->OnUnloaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnUnloaded>(this);
asset->RemoveReference();
}
}
@@ -70,8 +72,12 @@ void AssetReferenceBase::OnUnloaded(Asset* asset)
WeakAssetReferenceBase::~WeakAssetReferenceBase()
{
if (_asset)
_asset->OnUnloaded.Unbind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
Asset* asset = _asset;
if (asset)
{
_asset = nullptr;
asset->OnUnloaded.Unbind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
}
}
String WeakAssetReferenceBase::ToString() const
@@ -101,6 +107,20 @@ void WeakAssetReferenceBase::OnUnloaded(Asset* asset)
_asset = nullptr;
}
SoftAssetReferenceBase::~SoftAssetReferenceBase()
{
Asset* asset = _asset;
if (asset)
{
_asset = nullptr;
asset->OnUnloaded.Unbind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
asset->RemoveReference();
}
#if !BUILD_RELEASE
_id = Guid::Empty;
#endif
}
String SoftAssetReferenceBase::ToString() const
{
return _asset ? _asset->ToString() : (_id.IsValid() ? _id.ToString() : TEXT("<null>"));
@@ -290,6 +310,10 @@ void Asset::ChangeID(const Guid& newId)
if (!IsVirtual())
CRASH;
// ID has to be unique
if (Content::GetAsset(newId) != nullptr)
CRASH;
const Guid oldId = _id;
ManagedScriptingObject::ChangeID(newId);
Content::onAssetChangeId(this, oldId, newId);
@@ -418,12 +442,15 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
// Note: to reproduce this case just include material into material (use layering).
// So during loading first material it will wait for child materials loaded calling this function
const double timeoutInSeconds = timeoutInMilliseconds * 0.001;
const double startTime = Platform::GetTimeSeconds();
Task* task = loadingTask;
Array<ContentLoadTask*, InlinedAllocation<64>> localQueue;
while (!Engine::ShouldExit())
#define CHECK_CONDITIONS() (!Engine::ShouldExit() && (timeoutInSeconds <= 0.0 || Platform::GetTimeSeconds() - startTime < timeoutInSeconds))
do
{
// Try to execute content tasks
while (task->IsQueued() && !Engine::ShouldExit())
while (task->IsQueued() && CHECK_CONDITIONS())
{
// Dequeue task from the loading queue
ContentLoadTask* tmp;
@@ -474,7 +501,8 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
break;
}
}
}
} while (CHECK_CONDITIONS());
#undef CHECK_CONDITIONS
}
else
{
@@ -502,6 +530,14 @@ void Asset::InitAsVirtual()
void Asset::CancelStreaming()
{
// Cancel loading task but go over asset locker to prevent case if other load threads still loads asset while it's reimported on other thread
Locker.Lock();
ContentLoadTask* loadTask = _loadingTask;
Locker.Unlock();
if (loadTask)
{
loadTask->Cancel();
}
}
#if USE_EDITOR
@@ -538,11 +574,7 @@ ContentLoadTask* Asset::createLoadingTask()
void Asset::startLoading()
{
// Check if is already loaded
if (IsLoaded())
return;
// Start loading (using async tasks)
ASSERT(!IsLoaded());
ASSERT(_loadingTask == nullptr);
_loadingTask = createLoadingTask();
ASSERT(_loadingTask != nullptr);

View File

@@ -9,9 +9,6 @@
/// </summary>
class FLAXENGINE_API AssetReferenceBase
{
public:
typedef Delegate<> EventType;
protected:
Asset* _asset = nullptr;
@@ -19,17 +16,17 @@ public:
/// <summary>
/// The asset loaded event (fired when asset gets loaded or is already loaded after change).
/// </summary>
EventType Loaded;
Action Loaded;
/// <summary>
/// The asset unloading event (should cleanup refs to it).
/// </summary>
EventType Unload;
Action Unload;
/// <summary>
/// Action fired when field gets changed (link a new asset or change to the another value).
/// </summary>
EventType Changed;
Action Changed;
public:
NON_COPYABLE(AssetReferenceBase);

View File

@@ -20,6 +20,7 @@
#include "Engine/ShadersCompilation/Config.h"
#if BUILD_DEBUG
#include "Engine/Engine/Globals.h"
#include "Engine/Scripting/BinaryModule.h"
#endif
#endif
@@ -256,7 +257,9 @@ Asset::LoadResult Material::load()
#if BUILD_DEBUG && USE_EDITOR
// Dump generated material source to the temporary file
BinaryModule::Locker.Lock();
source.SaveToFile(Globals::ProjectCacheFolder / TEXT("material.txt"));
BinaryModule::Locker.Unlock();
#endif
// Encrypt source code

View File

@@ -34,7 +34,6 @@
#define CHECK_INVALID_BUFFER(model, buffer) \
if (buffer->IsValidFor(model) == false) \
{ \
LOG(Warning, "Invalid Model Instance Buffer size {0} for Model {1}. It should be {2}. Manual update to proper size.", buffer->Count(), model->ToString(), model->MaterialSlots.Count()); \
buffer->Setup(model); \
}
@@ -783,6 +782,7 @@ void Model::InitAsVirtual()
void Model::CancelStreaming()
{
Asset::CancelStreaming();
CancelStreamingTasks();
}

View File

@@ -23,7 +23,6 @@
#define CHECK_INVALID_BUFFER(model, buffer) \
if (buffer->IsValidFor(model) == false) \
{ \
LOG(Warning, "Invalid Skinned Model Instance Buffer size {0} for Skinned Model {1}. It should be {2}. Manual update to proper size.", buffer->Count(), model->ToString(), model->MaterialSlots.Count()); \
buffer->Setup(model); \
}
@@ -969,6 +968,7 @@ void SkinnedModel::InitAsVirtual()
void SkinnedModel::CancelStreaming()
{
Asset::CancelStreaming();
CancelStreamingTasks();
}

View File

@@ -892,6 +892,11 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
PrintStack(LogType::Error);
break;
}
if (boxBase->ID == 1)
{
value = instance;
break;
}
// TODO: check if instance is of event type (including inheritance)
// Add Visual Script method to the event bindings table
@@ -1428,6 +1433,10 @@ Asset::LoadResult VisualScript::load()
#if USE_EDITOR
if (_instances.HasItems())
{
// Mark as already loaded so any WaitForLoaded checks during GetDefaultInstance bellow will handle this Visual Script as ready to use
_loadFailed = false;
_isLoaded = true;
// Setup scripting type
CacheScriptingType();
@@ -1512,7 +1521,7 @@ void VisualScript::unload(bool isReloading)
// Note: preserve the registered scripting type but invalidate the locally cached handle
if (_scriptingTypeHandle)
{
VisualScriptingModule.Locker.Lock();
VisualScriptingBinaryModule::Locker.Lock();
auto& type = VisualScriptingModule.Types[_scriptingTypeHandle.TypeIndex];
if (type.Script.DefaultInstance)
{
@@ -1523,7 +1532,7 @@ void VisualScript::unload(bool isReloading)
VisualScriptingModule.Scripts[_scriptingTypeHandle.TypeIndex] = nullptr;
_scriptingTypeHandleCached = _scriptingTypeHandle;
_scriptingTypeHandle = ScriptingTypeHandle();
VisualScriptingModule.Locker.Unlock();
VisualScriptingBinaryModule::Locker.Unlock();
}
}
@@ -1534,8 +1543,8 @@ AssetChunksFlag VisualScript::getChunksToPreload() const
void VisualScript::CacheScriptingType()
{
ScopeLock lock(VisualScriptingBinaryModule::Locker);
auto& binaryModule = VisualScriptingModule;
ScopeLock lock(binaryModule.Locker);
// Find base type
const StringAnsi baseTypename(Meta.BaseTypename);

View File

@@ -150,9 +150,7 @@ void BinaryAsset::ClearDependencies()
{
auto asset = Cast<BinaryAsset>(Content::GetAsset(e.First));
if (asset)
{
asset->_dependantAssets.Remove(this);
}
}
Dependencies.Clear();
}
@@ -323,26 +321,29 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool
{
// Ensure path is in a valid format
String pathNorm(path);
FileSystem::NormalizePath(pathNorm);
ContentStorageManager::FormatPath(pathNorm);
const StringView filePath = pathNorm;
// Find target storage container and the asset
auto storage = ContentStorageManager::TryGetStorage(pathNorm);
auto asset = Content::GetAsset(pathNorm);
auto storage = ContentStorageManager::TryGetStorage(filePath);
auto asset = Content::GetAsset(filePath);
auto binaryAsset = dynamic_cast<BinaryAsset*>(asset);
if (asset && !binaryAsset)
{
LOG(Warning, "Cannot write to the non-binary asset location.");
return true;
}
if (!binaryAsset && !storage && FileSystem::FileExists(filePath))
{
// Force-resolve storage (asset at that path could be not yet loaded into registry)
storage = ContentStorageManager::GetStorage(filePath);
}
// Check if can perform write operation to the asset container
if (storage)
if (storage && !storage->AllowDataModifications())
{
if (!storage->AllowDataModifications())
{
LOG(Warning, "Cannot write to the asset storage container.");
return true;
}
LOG(Warning, "Cannot write to the asset storage container.");
return true;
}
// Initialize data container
@@ -352,6 +353,11 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool
// Use the same asset ID
data.Header.ID = binaryAsset->GetID();
}
else if (storage && storage->GetEntriesCount())
{
// Use the same file ID
data.Header.ID = storage->GetEntry(0).ID;
}
else
{
// Randomize ID
@@ -373,12 +379,22 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool
}
else
{
ASSERT(pathNorm.HasChars());
result = FlaxStorage::Create(pathNorm, data, silentMode);
ASSERT(filePath.HasChars());
result = FlaxStorage::Create(filePath, data, silentMode);
}
if (binaryAsset)
binaryAsset->_isSaving = false;
if (binaryAsset)
{
// Inform dependant asset (use cloned version because it might be modified by assets when they got reloaded)
auto dependantAssets = binaryAsset->_dependantAssets;
for (auto& e : dependantAssets)
{
e->OnDependencyModified(binaryAsset);
}
}
return result;
}

View File

@@ -54,8 +54,7 @@ namespace
// Assets
CriticalSection AssetsLocker;
Dictionary<Guid, Asset*> Assets(2048);
CriticalSection LoadCallAssetsLocker;
Array<Guid> LoadCallAssets(64);
Array<Guid> LoadCallAssets(PLATFORM_THREADS_LIMIT);
CriticalSection LoadedAssetsToInvokeLocker;
Array<Asset*> LoadedAssetsToInvoke(64);
Array<Asset*> ToUnload;
@@ -154,7 +153,7 @@ void ContentService::LateUpdate()
// Unload marked assets
for (int32 i = 0; i < ToUnload.Count(); i++)
{
Asset* asset = ToUnload[i];
Asset* asset = ToUnload[i];
// Check if has no references
if (asset->GetReferencesCount() <= 0)
@@ -231,6 +230,7 @@ bool Content::GetAssetInfo(const Guid& id, AssetInfo& info)
// Find asset in registry
if (Cache.FindAsset(id, info))
return true;
PROFILE_CPU();
// Locking injects some stalls but we need to make it safe (only one thread can pass though it at once)
ScopeLock lock(WorkspaceDiscoveryLocker);
@@ -277,6 +277,7 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
// Find asset in registry
if (Cache.FindAsset(path, info))
return true;
PROFILE_CPU();
const auto extension = FileSystem::GetExtension(path).ToLower();
@@ -449,18 +450,19 @@ Asset* Content::LoadAsync(const StringView& path, const ScriptingTypeHandle& typ
{
// Ensure path is in a valid format
String pathNorm(path);
StringUtils::PathRemoveRelativeParts(pathNorm);
ContentStorageManager::FormatPath(pathNorm);
const StringView filePath = pathNorm;
#if USE_EDITOR
if (!FileSystem::FileExists(pathNorm))
if (!FileSystem::FileExists(filePath))
{
LOG(Error, "Missing file \'{0}\'", pathNorm);
LOG(Error, "Missing file \'{0}\'", filePath);
return nullptr;
}
#endif
AssetInfo assetInfo;
if (GetAssetInfo(pathNorm, assetInfo))
if (GetAssetInfo(filePath, assetInfo))
{
return LoadAsync(assetInfo.ID, type);
}
@@ -538,6 +540,8 @@ void Content::DeleteAsset(Asset* asset)
void Content::DeleteAsset(const StringView& path)
{
PROFILE_CPU();
// Try to delete already loaded asset
Asset* asset = GetAsset(path);
if (asset != nullptr)
@@ -566,12 +570,12 @@ void Content::DeleteAsset(const StringView& path)
void Content::deleteFileSafety(const StringView& path, const Guid& id)
{
// Check if given id is invalid
if (!id.IsValid())
{
LOG(Warning, "Cannot remove file \'{0}\'. Given ID is invalid.", path);
return;
}
PROFILE_CPU();
// Ensure that file has the same ID (prevent from deleting different assets)
auto storage = ContentStorageManager::TryGetStorage(path);
@@ -678,6 +682,7 @@ bool Content::FastTmpAssetClone(const StringView& path, String& resultPath)
bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPath, const Guid& dstId)
{
PROFILE_CPU();
ASSERT(FileSystem::AreFilePathsEqual(srcPath, dstPath) == false && dstId.IsValid());
LOG(Info, "Cloning asset \'{0}\' to \'{1}\'({2}).", srcPath, dstPath, dstId);
@@ -697,13 +702,11 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
LOG(Warning, "Cannot copy file to destination.");
return true;
}
if (JsonStorageProxy::ChangeId(dstPath, dstId))
{
LOG(Warning, "Cannot change asset ID.");
return true;
}
return false;
}
@@ -768,12 +771,9 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
FileSystem::DeleteFile(tmpPath);
// Reload storage
if (auto storage = ContentStorageManager::GetStorage(dstPath))
{
auto storage = ContentStorageManager::GetStorage(dstPath);
if (storage)
{
storage->Reload();
}
storage->Reload();
}
}
@@ -801,6 +801,7 @@ Asset* Content::CreateVirtualAsset(MClass* type)
Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type)
{
PROFILE_CPU();
auto& assetType = type.GetType();
// Init mock asset info
@@ -911,84 +912,62 @@ bool Content::IsAssetTypeIdInvalid(const ScriptingTypeHandle& type, const Script
Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
{
// Early out
if (!id.IsValid())
{
// Back
return nullptr;
}
// Check if asset has been already loaded
Asset* result = GetAsset(id);
Asset* result = nullptr;
AssetsLocker.Lock();
Assets.TryGet(id, result);
if (result)
{
AssetsLocker.Unlock();
// Validate type
if (IsAssetTypeIdInvalid(type, result->GetTypeHandle()) && !result->Is(type))
{
LOG(Warning, "Different loaded asset type! Asset: \'{0}\'. Expected type: {1}", result->ToString(), type.ToString());
return nullptr;
}
return result;
}
// Check if that asset is during loading
LoadCallAssetsLocker.Lock();
if (LoadCallAssets.Contains(id))
{
LoadCallAssetsLocker.Unlock();
AssetsLocker.Unlock();
// Wait for load end
// TODO: dont use active waiting and prevent deadlocks if running on a main thread
//while (!Engine::ShouldExit())
while (true)
// Wait for loading end by other thread
bool contains = true;
while (contains)
{
LoadCallAssetsLocker.Lock();
const bool contains = LoadCallAssets.Contains(id);
LoadCallAssetsLocker.Unlock();
if (!contains)
{
return GetAsset(id);
}
Platform::Sleep(1);
AssetsLocker.Lock();
contains = LoadCallAssets.Contains(id);
AssetsLocker.Unlock();
}
}
else
{
// Mark asset as loading
LoadCallAssets.Add(id);
LoadCallAssetsLocker.Unlock();
Assets.TryGet(id, result);
return result;
}
// Load asset
AssetInfo assetInfo;
result = load(id, type, assetInfo);
// Mark asset as loading and release lock so other threads can load other assets
LoadCallAssets.Add(id);
AssetsLocker.Unlock();
// End loading
LoadCallAssetsLocker.Lock();
LoadCallAssets.Remove(id);
LoadCallAssetsLocker.Unlock();
#define LOAD_FAILED() AssetsLocker.Lock(); LoadCallAssets.Remove(id); AssetsLocker.Unlock(); return nullptr
return result;
}
Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& assetInfo)
{
// Get cached asset info (from registry)
AssetInfo assetInfo;
if (!GetAssetInfo(id, assetInfo))
{
LOG(Warning, "Invalid or missing asset ({0}, {1}).", id.ToString(Guid::FormatType::N), type.ToString());
return nullptr;
LOG(Warning, "Invalid or missing asset ({0}, {1}).", id, type.ToString());
LOAD_FAILED();
}
#if ASSETS_LOADING_EXTRA_VERIFICATION
if (!FileSystem::FileExists(assetInfo.Path))
{
LOG(Error, "Cannot find file '{0}'", assetInfo.Path);
return nullptr;
LOAD_FAILED();
}
#endif
@@ -997,38 +976,42 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
if (factory == nullptr)
{
LOG(Error, "Cannot find asset factory. Info: {0}", assetInfo.ToString());
return nullptr;
LOAD_FAILED();
}
// Create asset object
auto result = factory->New(assetInfo);
result = factory->New(assetInfo);
if (result == nullptr)
{
LOG(Error, "Cannot create asset object. Info: {0}", assetInfo.ToString());
return nullptr;
LOAD_FAILED();
}
ASSERT(result->GetID() == id);
#if ASSETS_LOADING_EXTRA_VERIFICATION
if (IsAssetTypeIdInvalid(type, result->GetTypeHandle()) && !result->Is(type))
{
LOG(Error, "Different loaded asset type! Asset: '{0}'. Expected type: {1}", assetInfo.ToString(), type.ToString());
LOG(Warning, "Different loaded asset type! Asset: '{0}'. Expected type: {1}", assetInfo.ToString(), type.ToString());
result->DeleteObject();
return nullptr;
LOAD_FAILED();
}
#endif
// Register asset
ASSERT(result->GetID() == id);
AssetsLocker.Lock();
#if ASSETS_LOADING_EXTRA_VERIFICATION
ASSERT(!Assets.ContainsKey(id));
#endif
Assets.Add(id, result);
AssetsLocker.Unlock();
// Start asset loading
result->startLoading();
// Remove from the loading queue and release lock
LoadCallAssets.Remove(id);
AssetsLocker.Unlock();
#undef LOAD_FAILED
return result;
}

View File

@@ -366,7 +366,6 @@ private:
static void onAssetLoaded(Asset* asset);
static void onAssetUnload(Asset* asset);
static void onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId);
static Asset* load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& assetInfo);
private:
static void deleteFileSafety(const StringView& path, const Guid& id);

View File

@@ -48,6 +48,8 @@ protected:
// [ContentLoadTask]
Result run() override
{
if (IsCancelRequested())
return Result::Ok;
PROFILE_CPU();
AssetReference<BinaryAsset> ref = _asset.Get();
@@ -67,8 +69,6 @@ protected:
{
if (IsCancelRequested())
return Result::Ok;
// Load it
#if TRACY_ENABLE
ZoneScoped;
ZoneName(*name, name.Length());

View File

@@ -31,10 +31,13 @@ public:
if (Asset)
{
Asset->Locker.Lock();
Asset->_loadFailed = true;
Asset->_isLoaded = false;
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
Asset->_loadingTask = nullptr;
if (Asset->_loadingTask == this)
{
Asset->_loadFailed = true;
Asset->_isLoaded = false;
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
Asset->_loadingTask = nullptr;
}
Asset->Locker.Unlock();
}
}
@@ -73,7 +76,10 @@ protected:
{
if (Asset)
{
Asset->_loadingTask = nullptr;
Asset->Locker.Lock();
if (Asset->_loadingTask == this)
Asset->_loadingTask = nullptr;
Asset->Locker.Unlock();
Asset = nullptr;
}
@@ -84,7 +90,10 @@ protected:
{
if (Asset)
{
Asset->_loadingTask = nullptr;
Asset->Locker.Lock();
if (Asset->_loadingTask == this)
Asset->_loadingTask = nullptr;
Asset->Locker.Unlock();
Asset = nullptr;
}

View File

@@ -30,9 +30,7 @@ public:
/// <summary>
/// Finalizes an instance of the <see cref="SoftAssetReferenceBase"/> class.
/// </summary>
~SoftAssetReferenceBase()
{
}
~SoftAssetReferenceBase();
public:
/// <summary>

View File

@@ -6,6 +6,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/TaskGraph.h"
@@ -185,6 +186,16 @@ void ContentStorageManager::EnsureUnlocked()
Locker.Unlock();
}
void ContentStorageManager::FormatPath(String& path)
{
StringUtils::PathRemoveRelativeParts(path);
if (FileSystem::IsRelative(path))
{
// Convert local-project paths into absolute format which is used by Content Storage system
path = Globals::ProjectFolder / path;
}
}
bool ContentStorageManager::IsFlaxStoragePath(const String& path)
{
auto extension = FileSystem::GetExtension(path).ToLower();

View File

@@ -75,6 +75,9 @@ public:
/// </summary>
static void EnsureUnlocked();
// Formats path into valid format used by the storage system (normalized and absolute).
static void FormatPath(String& path);
public:
/// <summary>
/// Determines whether the specified path can be a binary asset file (based on it's extension).

View File

@@ -562,6 +562,7 @@ bool FlaxStorage::Reload()
{
if (!IsLoaded())
return false;
PROFILE_CPU();
OnReloading(this);
@@ -728,7 +729,11 @@ bool FlaxStorage::ChangeAssetID(Entry& e, const Guid& newId)
}
// Close file
CloseFileHandles();
if (CloseFileHandles())
{
LOG(Error, "Cannot close file access for '{}'", _path);
return true;
}
// Change ID
// TODO: here we could extend it and load assets from the storage and call asset ID change event to change references
@@ -776,6 +781,8 @@ FlaxChunk* FlaxStorage::AllocateChunk()
bool FlaxStorage::Create(const StringView& path, const AssetInitData* data, int32 dataCount, bool silentMode, const CustomData* customData)
{
PROFILE_CPU();
ZoneText(*path, path.Length());
LOG(Info, "Creating package at \'{0}\'. Silent Mode: {1}", path, silentMode);
// Prepare to have access to the file
@@ -956,6 +963,7 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d
// Asset Dependencies
stream->WriteInt32(header.Dependencies.Count());
stream->WriteBytes(header.Dependencies.Get(), header.Dependencies.Count() * sizeof(Pair<Guid, DateTime>));
static_assert(sizeof(Pair<Guid, DateTime>) == sizeof(Guid) + sizeof(DateTime), "Invalid data size.");
}
#if ASSETS_LOADING_EXTRA_VERIFICATION
@@ -1296,21 +1304,23 @@ FileReadStream* FlaxStorage::OpenFile()
return stream;
}
void FlaxStorage::CloseFileHandles()
bool FlaxStorage::CloseFileHandles()
{
PROFILE_CPU();
// Note: this is usually called by the content manager when this file is not used or on exit
// In those situations all the async tasks using this storage should be cancelled externally
// Ensure that no one is using this resource
int32 waitTime = 10;
int32 waitTime = 100;
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
Platform::Sleep(10);
Platform::Sleep(1);
if (Platform::AtomicRead(&_chunksLock) != 0)
{
// File can be locked by some streaming tasks (eg. AudioClip::StreamingTask or StreamModelLODTask)
Entry e;
for (int32 i = 0; i < GetEntriesCount(); i++)
{
Entry e;
GetEntry(i, e);
Asset* asset = Content::GetAsset(e.ID);
if (asset)
@@ -1320,9 +1330,15 @@ void FlaxStorage::CloseFileHandles()
}
}
}
ASSERT(_chunksLock == 0);
waitTime = 100;
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
Platform::Sleep(1);
if (Platform::AtomicRead(&_chunksLock) != 0)
return true; // Failed, someone is still accessing the file
// Close file handles (from all threads)
_file.DeleteAll();
return false;
}
void FlaxStorage::Dispose()
@@ -1331,7 +1347,10 @@ void FlaxStorage::Dispose()
return;
// Close file
CloseFileHandles();
if (CloseFileHandles())
{
LOG(Error, "Cannot close file access for '{}'", _path);
}
// Release data
_chunks.ClearDelete();

View File

@@ -405,7 +405,7 @@ public:
/// <summary>
/// Closes the file handles (it can be modified from the outside).
/// </summary>
void CloseFileHandles();
bool CloseFileHandles();
/// <summary>
/// Releases storage resources and closes handle to the file.

View File

@@ -15,7 +15,7 @@
#include "Engine/Platform/Platform.h"
#include "Engine/Engine/Globals.h"
#include "ImportTexture.h"
#include "ImportModelFile.h"
#include "ImportModel.h"
#include "ImportAudio.h"
#include "ImportShader.h"
#include "ImportFont.h"
@@ -165,20 +165,7 @@ bool CreateAssetContext::AllocateChunk(int32 index)
void CreateAssetContext::AddMeta(JsonWriter& writer) const
{
writer.JKEY("ImportPath");
if (AssetsImportingManager::UseImportPathRelative && !FileSystem::IsRelative(InputPath)
#if PLATFORM_WINDOWS
// Import path from other drive should be stored as absolute on Windows to prevent issues
&& InputPath.Length() > 2 && Globals::ProjectFolder.Length() > 2 && InputPath[0] == Globals::ProjectFolder[0]
#endif
)
{
const String relativePath = FileSystem::ConvertAbsolutePathToRelative(Globals::ProjectFolder, InputPath);
writer.String(relativePath);
}
else
{
writer.String(InputPath);
}
writer.String(AssetsImportingManager::GetImportPath(InputPath));
writer.JKEY("ImportUsername");
writer.String(Platform::GetUserName());
}
@@ -189,7 +176,12 @@ void CreateAssetContext::ApplyChanges()
auto storage = ContentStorageManager::TryGetStorage(TargetAssetPath);
if (storage && storage->IsLoaded())
{
storage->CloseFileHandles();
if (storage->CloseFileHandles())
{
LOG(Error, "Cannot close file access for '{}'", TargetAssetPath);
_applyChangesResult = CreateAssetResult::CannotSaveFile;
return;
}
}
// Move file
@@ -304,8 +296,24 @@ bool AssetsImportingManager::ImportIfEdited(const StringView& inputPath, const S
return false;
}
String AssetsImportingManager::GetImportPath(const String& path)
{
if (UseImportPathRelative && !FileSystem::IsRelative(path)
#if PLATFORM_WINDOWS
// Import path from other drive should be stored as absolute on Windows to prevent issues
&& path.Length() > 2 && Globals::ProjectFolder.Length() > 2 && path[0] == Globals::ProjectFolder[0]
#endif
)
{
return FileSystem::ConvertAbsolutePathToRelative(Globals::ProjectFolder, path);
}
return path;
}
bool AssetsImportingManager::Create(const Function<CreateAssetResult(CreateAssetContext&)>& callback, const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg)
{
PROFILE_CPU();
ZoneText(*outputPath, outputPath.Length());
const auto startTime = Platform::GetTimeSeconds();
// Pick ID if not specified
@@ -369,7 +377,7 @@ bool AssetsImportingManager::Create(const Function<CreateAssetResult(CreateAsset
if (result == CreateAssetResult::Ok)
{
// Register asset
Content::GetRegistry()->RegisterAsset(context.Data.Header, outputPath);
Content::GetRegistry()->RegisterAsset(context.Data.Header, context.TargetAssetPath);
// Done
const auto endTime = Platform::GetTimeSeconds();
@@ -380,7 +388,7 @@ bool AssetsImportingManager::Create(const Function<CreateAssetResult(CreateAsset
// Do nothing
return true;
}
else
else if (result != CreateAssetResult::Skip)
{
LOG(Error, "Cannot import file '{0}'! Result: {1}", inputPath, ::ToString(result));
return true;
@@ -425,37 +433,37 @@ bool AssetsImportingManagerService::Init()
{ TEXT("otf"), ASSET_FILES_EXTENSION, ImportFont::Import },
// Models
{ TEXT("obj"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("fbx"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("x"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("dae"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("gltf"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("glb"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("obj"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("fbx"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("x"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("dae"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("gltf"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("glb"), ASSET_FILES_EXTENSION, ImportModel::Import },
// gettext PO files
{ TEXT("po"), TEXT("json"), CreateJson::ImportPo },
// Models (untested formats - may fail :/)
{ TEXT("blend"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("bvh"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("ase"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("ply"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("dxf"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("ifc"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("nff"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("smd"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("vta"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("mdl"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("md2"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("md3"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("md5mesh"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("q3o"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("q3s"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("ac"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("stl"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("lwo"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("lws"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("lxo"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("blend"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("bvh"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("ase"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("ply"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("dxf"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("ifc"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("nff"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("smd"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("vta"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("mdl"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("md2"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("md3"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("md5mesh"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("q3o"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("q3s"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("ac"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("stl"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("lwo"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("lws"), ASSET_FILES_EXTENSION, ImportModel::Import },
{ TEXT("lxo"), ASSET_FILES_EXTENSION, ImportModel::Import },
};
AssetsImportingManager::Importers.Add(InBuildImporters, ARRAY_COUNT(InBuildImporters));
@@ -473,7 +481,7 @@ bool AssetsImportingManagerService::Init()
{ AssetsImportingManager::CreateMaterialInstanceTag, CreateMaterialInstance::Create },
// Models
{ AssetsImportingManager::CreateModelTag, ImportModelFile::Create },
{ AssetsImportingManager::CreateModelTag, ImportModel::Create },
// Other
{ AssetsImportingManager::CreateRawDataTag, CreateRawData::Create },

View File

@@ -236,6 +236,9 @@ public:
return ImportIfEdited(inputPath, outputPath, id, arg);
}
// Converts source files path into the relative format if enabled by the project settings. Result path can be stored in asset for reimports.
static String GetImportPath(const String& path);
private:
static bool Create(const CreateAssetFunction& callback, const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg);
};

View File

@@ -53,7 +53,7 @@ bool CreateJson::Create(const StringView& path, const StringAnsiView& data, cons
{
if (FileSystem::CreateDirectory(directory))
{
LOG(Warning, "Failed to create directory");
LOG(Warning, "Failed to create directory '{}'", directory);
return true;
}
}

View File

@@ -0,0 +1,743 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "ImportModel.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Core/Log.h"
#include "Engine/Core/Collections/Sorting.h"
#include "Engine/Core/Collections/ArrayExtensions.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Graphics/Models/ModelData.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Content/Assets/SkinnedModel.h"
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Assets/Animation.h"
#include "Engine/Content/Content.h"
#include "Engine/Level/Actors/EmptyActor.h"
#include "Engine/Level/Actors/StaticModel.h"
#include "Engine/Level/Prefabs/Prefab.h"
#include "Engine/Level/Prefabs/PrefabManager.h"
#include "Engine/Level/Scripts/ModelPrefab.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Utilities/RectPack.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "AssetsImportingManager.h"
bool ImportModel::TryGetImportOptions(const StringView& path, Options& options)
{
if (FileSystem::FileExists(path))
{
// Try to load asset file and asset info
auto tmpFile = ContentStorageManager::GetStorage(path);
AssetInitData data;
if (tmpFile
&& tmpFile->GetEntriesCount() == 1
&& (
(tmpFile->GetEntry(0).TypeName == Model::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 4)
||
(tmpFile->GetEntry(0).TypeName == SkinnedModel::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 1)
||
(tmpFile->GetEntry(0).TypeName == Animation::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 1)
))
{
// Check import meta
rapidjson_flax::Document metadata;
metadata.Parse((const char*)data.Metadata.Get(), data.Metadata.Length());
if (metadata.HasParseError() == false)
{
options.Deserialize(metadata, nullptr);
return true;
}
}
}
return false;
}
struct PrefabObject
{
int32 NodeIndex;
String Name;
String AssetPath;
};
void RepackMeshLightmapUVs(ModelData& data)
{
// Use weight-based coordinates space placement and rect-pack to allocate more space for bigger meshes in the model lightmap chart
int32 lodIndex = 0;
auto& lod = data.LODs[lodIndex];
// Build list of meshes with their area
struct LightmapUVsPack : RectPack<LightmapUVsPack, float>
{
LightmapUVsPack(float x, float y, float width, float height)
: RectPack<LightmapUVsPack, float>(x, y, width, height)
{
}
void OnInsert()
{
}
};
struct MeshEntry
{
MeshData* Mesh;
float Area;
float Size;
LightmapUVsPack* Slot;
};
Array<MeshEntry> entries;
entries.Resize(lod.Meshes.Count());
float areaSum = 0;
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
{
auto& entry = entries[meshIndex];
entry.Mesh = lod.Meshes[meshIndex];
entry.Area = entry.Mesh->CalculateTrianglesArea();
entry.Size = Math::Sqrt(entry.Area);
areaSum += entry.Area;
}
if (areaSum > ZeroTolerance)
{
// Pack all surfaces into atlas
float atlasSize = Math::Sqrt(areaSum) * 1.02f;
int32 triesLeft = 10;
while (triesLeft--)
{
bool failed = false;
const float chartsPadding = (4.0f / 256.0f) * atlasSize;
LightmapUVsPack root(chartsPadding, chartsPadding, atlasSize - chartsPadding, atlasSize - chartsPadding);
for (auto& entry : entries)
{
entry.Slot = root.Insert(entry.Size, entry.Size, chartsPadding);
if (entry.Slot == nullptr)
{
// Failed to insert surface, increase atlas size and try again
atlasSize *= 1.5f;
failed = true;
break;
}
}
if (!failed)
{
// Transform meshes lightmap UVs into the slots in the whole atlas
const float atlasSizeInv = 1.0f / atlasSize;
for (const auto& entry : entries)
{
Float2 uvOffset(entry.Slot->X * atlasSizeInv, entry.Slot->Y * atlasSizeInv);
Float2 uvScale((entry.Slot->Width - chartsPadding) * atlasSizeInv, (entry.Slot->Height - chartsPadding) * atlasSizeInv);
// TODO: SIMD
for (auto& uv : entry.Mesh->LightmapUVs)
{
uv = uv * uvScale + uvOffset;
}
}
break;
}
}
}
}
void TryRestoreMaterials(CreateAssetContext& context, ModelData& modelData)
{
// Skip if file is missing
if (!FileSystem::FileExists(context.TargetAssetPath))
return;
// Try to load asset that gets reimported
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
if (asset == nullptr)
return;
if (asset->WaitForLoaded())
return;
// Get model object
ModelBase* model = nullptr;
if (asset.Get()->GetTypeName() == Model::TypeName)
{
model = ((Model*)asset.Get());
}
else if (asset.Get()->GetTypeName() == SkinnedModel::TypeName)
{
model = ((SkinnedModel*)asset.Get());
}
if (!model)
return;
// Peek materials
for (int32 i = 0; i < modelData.Materials.Count(); i++)
{
auto& dstSlot = modelData.Materials[i];
if (model->MaterialSlots.Count() > i)
{
auto& srcSlot = model->MaterialSlots[i];
dstSlot.Name = srcSlot.Name;
dstSlot.ShadowsMode = srcSlot.ShadowsMode;
dstSlot.AssetID = srcSlot.Material.GetID();
}
}
}
void SetupMaterialSlots(ModelData& data, const Array<MaterialSlotEntry>& materials)
{
Array<int32> materialSlotsTable;
materialSlotsTable.Resize(materials.Count());
materialSlotsTable.SetAll(-1);
for (auto& lod : data.LODs)
{
for (MeshData* mesh : lod.Meshes)
{
int32 newSlotIndex = materialSlotsTable[mesh->MaterialSlotIndex];
if (newSlotIndex == -1)
{
newSlotIndex = data.Materials.Count();
data.Materials.AddOne() = materials[mesh->MaterialSlotIndex];
}
mesh->MaterialSlotIndex = newSlotIndex;
}
}
}
bool SortMeshGroups(IGrouping<StringView, MeshData*> const& i1, IGrouping<StringView, MeshData*> const& i2)
{
return i1.GetKey().Compare(i2.GetKey()) < 0;
}
CreateAssetResult ImportModel::Import(CreateAssetContext& context)
{
// Get import options
Options options;
if (context.CustomArg != nullptr)
{
// Copy import options from argument
options = *static_cast<Options*>(context.CustomArg);
}
else
{
// Restore the previous settings or use default ones
if (!TryGetImportOptions(context.TargetAssetPath, options))
{
LOG(Warning, "Missing model import options. Using default values.");
}
}
// Import model file
ModelData* data = options.Cached ? options.Cached->Data : nullptr;
ModelData dataThis;
Array<IGrouping<StringView, MeshData*>>* meshesByNamePtr = options.Cached ? (Array<IGrouping<StringView, MeshData*>>*)options.Cached->MeshesByName : nullptr;
Array<IGrouping<StringView, MeshData*>> meshesByNameThis;
String autoImportOutput;
if (!data)
{
String errorMsg;
autoImportOutput = StringUtils::GetDirectoryName(context.TargetAssetPath);
autoImportOutput /= options.SubAssetFolder.HasChars() ? options.SubAssetFolder.TrimTrailing() : String(StringUtils::GetFileNameWithoutExtension(context.InputPath));
if (ModelTool::ImportModel(context.InputPath, dataThis, options, errorMsg, autoImportOutput))
{
LOG(Error, "Cannot import model file. {0}", errorMsg);
return CreateAssetResult::Error;
}
data = &dataThis;
// Group meshes by the name (the same mesh name can be used by multiple meshes that use different materials)
if (data->LODs.Count() != 0)
{
const Function<StringView(MeshData* const&)> f = [](MeshData* const& x) -> StringView
{
return x->Name;
};
ArrayExtensions::GroupBy(data->LODs[0].Meshes, f, meshesByNameThis);
Sorting::QuickSort(meshesByNameThis.Get(), meshesByNameThis.Count(), &SortMeshGroups);
}
meshesByNamePtr = &meshesByNameThis;
}
Array<IGrouping<StringView, MeshData*>>& meshesByName = *meshesByNamePtr;
// Import objects from file separately
ModelTool::Options::CachedData cached = { data, (void*)meshesByNamePtr };
Array<PrefabObject> prefabObjects;
if (options.Type == ModelTool::ModelType::Prefab)
{
// Normalize options
options.SplitObjects = false;
options.ObjectIndex = -1;
// Import all of the objects recursive but use current model data to skip loading file again
options.Cached = &cached;
Function<bool(Options& splitOptions, const StringView& objectName, String& outputPath)> splitImport = [&context, &autoImportOutput](Options& splitOptions, const StringView& objectName, String& outputPath)
{
// Recursive importing of the split object
String postFix = objectName;
const int32 splitPos = postFix.FindLast(TEXT('|'));
if (splitPos != -1)
postFix = postFix.Substring(splitPos + 1);
// TODO: check for name collisions with material/texture assets
outputPath = autoImportOutput / String(StringUtils::GetFileNameWithoutExtension(context.TargetAssetPath)) + TEXT(" ") + postFix + TEXT(".flax");
splitOptions.SubAssetFolder = TEXT(" "); // Use the same folder as asset as they all are imported to the subdir for the prefab (see SubAssetFolder usage above)
return AssetsImportingManager::Import(context.InputPath, outputPath, &splitOptions);
};
auto splitOptions = options;
LOG(Info, "Splitting imported {0} meshes", meshesByName.Count());
PrefabObject prefabObject;
for (int32 groupIndex = 0; groupIndex < meshesByName.Count(); groupIndex++)
{
auto& group = meshesByName[groupIndex];
// Cache object options (nested sub-object import removes the meshes)
prefabObject.NodeIndex = group.First()->NodeIndex;
prefabObject.Name = group.First()->Name;
splitOptions.Type = ModelTool::ModelType::Model;
splitOptions.ObjectIndex = groupIndex;
if (!splitImport(splitOptions, group.GetKey(), prefabObject.AssetPath))
{
prefabObjects.Add(prefabObject);
}
}
LOG(Info, "Splitting imported {0} animations", data->Animations.Count());
for (int32 i = 0; i < data->Animations.Count(); i++)
{
auto& animation = data->Animations[i];
splitOptions.Type = ModelTool::ModelType::Animation;
splitOptions.ObjectIndex = i;
splitImport(splitOptions, animation.Name, prefabObject.AssetPath);
}
}
else if (options.SplitObjects)
{
// Import the first object within this call
options.SplitObjects = false;
options.ObjectIndex = 0;
// Import rest of the objects recursive but use current model data to skip loading file again
options.Cached = &cached;
Function<bool(Options& splitOptions, const StringView& objectName)> splitImport;
splitImport.Bind([&context](Options& splitOptions, const StringView& objectName)
{
// Recursive importing of the split object
String postFix = objectName;
const int32 splitPos = postFix.FindLast(TEXT('|'));
if (splitPos != -1)
postFix = postFix.Substring(splitPos + 1);
const String outputPath = String(StringUtils::GetPathWithoutExtension(context.TargetAssetPath)) + TEXT(" ") + postFix + TEXT(".flax");
return AssetsImportingManager::Import(context.InputPath, outputPath, &splitOptions);
});
auto splitOptions = options;
switch (options.Type)
{
case ModelTool::ModelType::Model:
case ModelTool::ModelType::SkinnedModel:
LOG(Info, "Splitting imported {0} meshes", meshesByName.Count());
for (int32 groupIndex = 1; groupIndex < meshesByName.Count(); groupIndex++)
{
auto& group = meshesByName[groupIndex];
splitOptions.ObjectIndex = groupIndex;
splitImport(splitOptions, group.GetKey());
}
break;
case ModelTool::ModelType::Animation:
LOG(Info, "Splitting imported {0} animations", data->Animations.Count());
for (int32 i = 1; i < data->Animations.Count(); i++)
{
auto& animation = data->Animations[i];
splitOptions.ObjectIndex = i;
splitImport(splitOptions, animation.Name);
}
break;
}
}
// When importing a single object as model asset then select a specific mesh group
Array<MeshData*> meshesToDelete;
if (options.ObjectIndex >= 0 &&
options.ObjectIndex < meshesByName.Count() &&
(options.Type == ModelTool::ModelType::Model || options.Type == ModelTool::ModelType::SkinnedModel))
{
auto& group = meshesByName[options.ObjectIndex];
if (&dataThis == data)
{
// Use meshes only from the the grouping (others will be removed manually)
{
auto& lod = dataThis.LODs[0];
meshesToDelete.Add(lod.Meshes);
lod.Meshes.Clear();
for (MeshData* mesh : group)
{
lod.Meshes.Add(mesh);
meshesToDelete.Remove(mesh);
}
}
for (int32 lodIndex = 1; lodIndex < dataThis.LODs.Count(); lodIndex++)
{
auto& lod = dataThis.LODs[lodIndex];
Array<MeshData*> lodMeshes = lod.Meshes;
lod.Meshes.Clear();
for (MeshData* lodMesh : lodMeshes)
{
if (lodMesh->Name == group.GetKey())
lod.Meshes.Add(lodMesh);
else
meshesToDelete.Add(lodMesh);
}
}
// Use only materials references by meshes from the first grouping
{
auto materials = dataThis.Materials;
dataThis.Materials.Clear();
SetupMaterialSlots(dataThis, materials);
}
}
else
{
// Copy data from others data
dataThis.Skeleton = data->Skeleton;
dataThis.Nodes = data->Nodes;
// Move meshes from this group (including any LODs of them)
{
auto& lod = dataThis.LODs.AddOne();
lod.ScreenSize = data->LODs[0].ScreenSize;
lod.Meshes.Add(group);
for (MeshData* mesh : group)
data->LODs[0].Meshes.Remove(mesh);
}
for (int32 lodIndex = 1; lodIndex < data->LODs.Count(); lodIndex++)
{
Array<MeshData*> lodMeshes = data->LODs[lodIndex].Meshes;
for (int32 i = lodMeshes.Count() - 1; i >= 0; i--)
{
MeshData* lodMesh = lodMeshes[i];
if (lodMesh->Name == group.GetKey())
data->LODs[lodIndex].Meshes.Remove(lodMesh);
else
lodMeshes.RemoveAtKeepOrder(i);
}
if (lodMeshes.Count() == 0)
break; // No meshes of that name in this LOD so skip further ones
auto& lod = dataThis.LODs.AddOne();
lod.ScreenSize = data->LODs[lodIndex].ScreenSize;
lod.Meshes.Add(lodMeshes);
}
// Copy materials used by the meshes
SetupMaterialSlots(dataThis, data->Materials);
}
data = &dataThis;
}
// Check if restore materials on model reimport
if (options.RestoreMaterialsOnReimport && data->Materials.HasItems())
{
TryRestoreMaterials(context, *data);
}
// When using generated lightmap UVs those coordinates needs to be moved so all meshes are in unique locations in [0-1]x[0-1] coordinates space
// Model importer generates UVs in [0-1] space for each mesh so now we need to pack them inside the whole model (only when using multiple meshes)
if (options.Type == ModelTool::ModelType::Model && options.LightmapUVsSource == ModelLightmapUVsSource::Generate && data->LODs.HasItems() && data->LODs[0].Meshes.Count() > 1)
{
RepackMeshLightmapUVs(*data);
}
// Create destination asset type
CreateAssetResult result = CreateAssetResult::InvalidTypeID;
switch (options.Type)
{
case ModelTool::ModelType::Model:
result = CreateModel(context, *data, &options);
break;
case ModelTool::ModelType::SkinnedModel:
result = CreateSkinnedModel(context, *data, &options);
break;
case ModelTool::ModelType::Animation:
result = CreateAnimation(context, *data, &options);
break;
case ModelTool::ModelType::Prefab:
result = CreatePrefab(context, *data, options, prefabObjects);
break;
}
for (auto mesh : meshesToDelete)
Delete(mesh);
if (result != CreateAssetResult::Ok)
return result;
// Create json with import context
rapidjson_flax::StringBuffer importOptionsMetaBuffer;
importOptionsMetaBuffer.Reserve(256);
CompactJsonWriter importOptionsMetaObj(importOptionsMetaBuffer);
JsonWriter& importOptionsMeta = importOptionsMetaObj;
importOptionsMeta.StartObject();
{
context.AddMeta(importOptionsMeta);
options.Serialize(importOptionsMeta, nullptr);
}
importOptionsMeta.EndObject();
context.Data.Metadata.Copy((const byte*)importOptionsMetaBuffer.GetString(), (uint32)importOptionsMetaBuffer.GetSize());
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModel::Create(CreateAssetContext& context)
{
ASSERT(context.CustomArg != nullptr);
auto& modelData = *(ModelData*)context.CustomArg;
// Ensure model has any meshes
if ((modelData.LODs.IsEmpty() || modelData.LODs[0].Meshes.IsEmpty()))
{
LOG(Warning, "Models has no valid meshes");
return CreateAssetResult::Error;
}
// Auto calculate LODs transition settings
modelData.CalculateLODsScreenSizes();
return CreateModel(context, modelData);
}
CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
PROFILE_CPU();
IMPORT_SETUP(Model, Model::SerializedVersion);
// Save model header
MemoryWriteStream stream(4096);
if (modelData.Pack2ModelHeader(&stream))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
// Pack model LODs data
const auto lodCount = modelData.GetLODsCount();
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
{
stream.SetPosition(0);
// Pack meshes
auto& meshes = modelData.LODs[lodIndex].Meshes;
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
{
if (meshes[meshIndex]->Pack2Model(&stream))
{
LOG(Warning, "Cannot pack mesh.");
return CreateAssetResult::Error;
}
}
const int32 chunkIndex = lodIndex + 1;
if (context.AllocateChunk(chunkIndex))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
// Generate SDF
if (options && options->GenerateSDF)
{
stream.SetPosition(0);
if (!ModelTool::GenerateModelSDF(nullptr, &modelData, options->SDFResolution, lodCount - 1, nullptr, &stream, context.TargetAssetPath))
{
if (context.AllocateChunk(15))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[15]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
}
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModel::CreateSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
PROFILE_CPU();
IMPORT_SETUP(SkinnedModel, SkinnedModel::SerializedVersion);
// Save skinned model header
MemoryWriteStream stream(4096);
if (modelData.Pack2SkinnedModelHeader(&stream))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
// Pack model LODs data
const auto lodCount = modelData.GetLODsCount();
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
{
stream.SetPosition(0);
// Mesh Data Version
stream.WriteByte(1);
// Pack meshes
auto& meshes = modelData.LODs[lodIndex].Meshes;
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
{
if (meshes[meshIndex]->Pack2SkinnedModel(&stream))
{
LOG(Warning, "Cannot pack mesh.");
return CreateAssetResult::Error;
}
}
const int32 chunkIndex = lodIndex + 1;
if (context.AllocateChunk(chunkIndex))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
PROFILE_CPU();
IMPORT_SETUP(Animation, Animation::SerializedVersion);
// Save animation data
MemoryWriteStream stream(8182);
const int32 animIndex = options && options->ObjectIndex != -1 ? options->ObjectIndex : 0; // Single animation per asset
if (modelData.Pack2AnimationHeader(&stream, animIndex))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, ModelData& data, const Options& options, const Array<PrefabObject>& prefabObjects)
{
PROFILE_CPU();
if (data.Nodes.Count() == 0)
return CreateAssetResult::Error;
// If that prefab already exists then we need to use it as base to preserve object IDs and local changes applied by user
const String outputPath = String(StringUtils::GetPathWithoutExtension(context.TargetAssetPath)) + DEFAULT_PREFAB_EXTENSION_DOT;
auto* prefab = FileSystem::FileExists(outputPath) ? Content::Load<Prefab>(outputPath) : nullptr;
if (prefab)
{
// Ensure that prefab has Default Instance so ObjectsCache is valid (used below)
prefab->GetDefaultInstance();
}
// Create prefab structure
Dictionary<int32, Actor*> nodeToActor;
Array<Actor*> nodeActors;
Actor* rootActor = nullptr;
for (int32 nodeIndex = 0; nodeIndex < data.Nodes.Count(); nodeIndex++)
{
const auto& node = data.Nodes[nodeIndex];
// Create actor(s) for this node
nodeActors.Clear();
for (const PrefabObject& e : prefabObjects)
{
if (e.NodeIndex == nodeIndex)
{
auto* actor = New<StaticModel>();
actor->SetName(e.Name);
if (auto* model = Content::LoadAsync<Model>(e.AssetPath))
{
actor->Model = model;
}
nodeActors.Add(actor);
}
}
Actor* nodeActor = nodeActors.Count() == 1 ? nodeActors[0] : New<EmptyActor>();
if (nodeActors.Count() > 1)
{
for (Actor* e : nodeActors)
{
e->SetParent(nodeActor);
}
}
if (nodeActors.Count() != 1)
{
// Include default actor to iterate over it properly in code below
nodeActors.Add(nodeActor);
}
// Setup node in hierarchy
nodeToActor.Add(nodeIndex, nodeActor);
nodeActor->SetName(node.Name);
nodeActor->SetLocalTransform(node.LocalTransform);
if (nodeIndex == 0)
{
// Special case for root actor to link any unlinked nodes
nodeToActor.Add(-1, nodeActor);
rootActor = nodeActor;
}
else
{
Actor* parentActor;
if (nodeToActor.TryGet(node.ParentIndex, parentActor))
nodeActor->SetParent(parentActor);
}
// Link with object from prefab (if reimporting)
if (prefab)
{
for (Actor* a : nodeActors)
{
for (const auto& i : prefab->ObjectsCache)
{
if (i.Value->GetTypeHandle() != a->GetTypeHandle()) // Type match
continue;
auto* o = (Actor*)i.Value;
if (o->GetName() != a->GetName()) // Name match
continue;
// Mark as this object already exists in prefab so will be preserved when updating it
a->LinkPrefab(o->GetPrefabID(), o->GetPrefabObjectID());
break;
}
}
}
}
ASSERT_LOW_LAYER(rootActor);
{
// Add script with import options
auto* modelPrefabScript = New<ModelPrefab>();
modelPrefabScript->SetParent(rootActor);
modelPrefabScript->ImportPath = AssetsImportingManager::GetImportPath(context.InputPath);
modelPrefabScript->ImportOptions = options;
// Link with existing prefab instance
if (prefab)
{
for (const auto& i : prefab->ObjectsCache)
{
if (i.Value->GetTypeHandle() == modelPrefabScript->GetTypeHandle())
{
modelPrefabScript->LinkPrefab(i.Value->GetPrefabID(), i.Value->GetPrefabObjectID());
break;
}
}
}
}
// Create prefab instead of native asset
bool failed;
if (prefab)
{
failed = prefab->ApplyAll(rootActor);
}
else
{
failed = PrefabManager::CreatePrefab(rootActor, outputPath, false);
}
// Cleanup objects from memory
rootActor->DeleteObjectNow();
if (failed)
return CreateAssetResult::Error;
return CreateAssetResult::Skip;
}
#endif

View File

@@ -8,15 +8,10 @@
#include "Engine/Tools/ModelTool/ModelTool.h"
/// <summary>
/// Enable/disable caching model import options
/// </summary>
#define IMPORT_MODEL_CACHE_OPTIONS 1
/// <summary>
/// Importing models utility
/// </summary>
class ImportModelFile
class ImportModel
{
public:
typedef ModelTool::Options Options;
@@ -45,9 +40,10 @@ public:
static CreateAssetResult Create(CreateAssetContext& context);
private:
static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
static CreateAssetResult CreateModel(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
static CreateAssetResult CreateSkinnedModel(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
static CreateAssetResult CreateAnimation(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
static CreateAssetResult CreatePrefab(CreateAssetContext& context, ModelData& data, const Options& options, const Array<struct PrefabObject>& prefabObjects);
};
#endif

View File

@@ -1,306 +0,0 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "ImportModel.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Core/Log.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Graphics/Models/ModelData.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Content/Assets/SkinnedModel.h"
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Assets/Animation.h"
#include "Engine/Content/Content.h"
#include "Engine/Platform/FileSystem.h"
#include "AssetsImportingManager.h"
bool ImportModelFile::TryGetImportOptions(const StringView& path, Options& options)
{
#if IMPORT_MODEL_CACHE_OPTIONS
if (FileSystem::FileExists(path))
{
// Try to load asset file and asset info
auto tmpFile = ContentStorageManager::GetStorage(path);
AssetInitData data;
if (tmpFile
&& tmpFile->GetEntriesCount() == 1
&& (
(tmpFile->GetEntry(0).TypeName == Model::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 4)
||
(tmpFile->GetEntry(0).TypeName == SkinnedModel::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 1)
||
(tmpFile->GetEntry(0).TypeName == Animation::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 1)
))
{
// Check import meta
rapidjson_flax::Document metadata;
metadata.Parse((const char*)data.Metadata.Get(), data.Metadata.Length());
if (metadata.HasParseError() == false)
{
options.Deserialize(metadata, nullptr);
return true;
}
}
}
#endif
return false;
}
void TryRestoreMaterials(CreateAssetContext& context, ModelData& modelData)
{
// Skip if file is missing
if (!FileSystem::FileExists(context.TargetAssetPath))
return;
// Try to load asset that gets reimported
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
if (asset == nullptr)
return;
if (asset->WaitForLoaded())
return;
// Get model object
ModelBase* model = nullptr;
if (asset.Get()->GetTypeName() == Model::TypeName)
{
model = ((Model*)asset.Get());
}
else if (asset.Get()->GetTypeName() == SkinnedModel::TypeName)
{
model = ((SkinnedModel*)asset.Get());
}
if (!model)
return;
// Peek materials
for (int32 i = 0; i < modelData.Materials.Count(); i++)
{
auto& dstSlot = modelData.Materials[i];
if (model->MaterialSlots.Count() > i)
{
auto& srcSlot = model->MaterialSlots[i];
dstSlot.Name = srcSlot.Name;
dstSlot.ShadowsMode = srcSlot.ShadowsMode;
dstSlot.AssetID = srcSlot.Material.GetID();
}
}
}
CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
{
// Get import options
Options options;
if (context.CustomArg != nullptr)
{
// Copy import options from argument
options = *static_cast<Options*>(context.CustomArg);
}
else
{
// Restore the previous settings or use default ones
if (!TryGetImportOptions(context.TargetAssetPath, options))
{
LOG(Warning, "Missing model import options. Using default values.");
}
}
if (options.SplitObjects)
{
options.OnSplitImport.Bind([&context](Options& splitOptions, const String& objectName)
{
// Recursive importing of the split object
String postFix = objectName;
const int32 splitPos = postFix.FindLast(TEXT('|'));
if (splitPos != -1)
postFix = postFix.Substring(splitPos + 1);
const String outputPath = String(StringUtils::GetPathWithoutExtension(context.TargetAssetPath)) + TEXT(" ") + postFix + TEXT(".flax");
return AssetsImportingManager::Import(context.InputPath, outputPath, &splitOptions);
});
}
// Import model file
ModelData modelData;
String errorMsg;
String autoImportOutput = String(StringUtils::GetDirectoryName(context.TargetAssetPath)) / StringUtils::GetFileNameWithoutExtension(context.InputPath);
if (ModelTool::ImportModel(context.InputPath, modelData, options, errorMsg, autoImportOutput))
{
LOG(Error, "Cannot import model file. {0}", errorMsg);
return CreateAssetResult::Error;
}
// Check if restore materials on model reimport
if (options.RestoreMaterialsOnReimport && modelData.Materials.HasItems())
{
TryRestoreMaterials(context, modelData);
}
// Auto calculate LODs transition settings
modelData.CalculateLODsScreenSizes();
// Create destination asset type
CreateAssetResult result = CreateAssetResult::InvalidTypeID;
switch (options.Type)
{
case ModelTool::ModelType::Model:
result = ImportModel(context, modelData, &options);
break;
case ModelTool::ModelType::SkinnedModel:
result = ImportSkinnedModel(context, modelData, &options);
break;
case ModelTool::ModelType::Animation:
result = ImportAnimation(context, modelData, &options);
break;
}
if (result != CreateAssetResult::Ok)
return result;
#if IMPORT_MODEL_CACHE_OPTIONS
// Create json with import context
rapidjson_flax::StringBuffer importOptionsMetaBuffer;
importOptionsMetaBuffer.Reserve(256);
CompactJsonWriter importOptionsMetaObj(importOptionsMetaBuffer);
JsonWriter& importOptionsMeta = importOptionsMetaObj;
importOptionsMeta.StartObject();
{
context.AddMeta(importOptionsMeta);
options.Serialize(importOptionsMeta, nullptr);
}
importOptionsMeta.EndObject();
context.Data.Metadata.Copy((const byte*)importOptionsMetaBuffer.GetString(), (uint32)importOptionsMetaBuffer.GetSize());
#endif
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModelFile::Create(CreateAssetContext& context)
{
ASSERT(context.CustomArg != nullptr);
auto& modelData = *(ModelData*)context.CustomArg;
// Ensure model has any meshes
if ((modelData.LODs.IsEmpty() || modelData.LODs[0].Meshes.IsEmpty()))
{
LOG(Warning, "Models has no valid meshes");
return CreateAssetResult::Error;
}
// Auto calculate LODs transition settings
modelData.CalculateLODsScreenSizes();
// Import
return ImportModel(context, modelData);
}
CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
// Base
IMPORT_SETUP(Model, Model::SerializedVersion);
// Save model header
MemoryWriteStream stream(4096);
if (modelData.Pack2ModelHeader(&stream))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
// Pack model LODs data
const auto lodCount = modelData.GetLODsCount();
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
{
stream.SetPosition(0);
// Pack meshes
auto& meshes = modelData.LODs[lodIndex].Meshes;
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
{
if (meshes[meshIndex]->Pack2Model(&stream))
{
LOG(Warning, "Cannot pack mesh.");
return CreateAssetResult::Error;
}
}
const int32 chunkIndex = lodIndex + 1;
if (context.AllocateChunk(chunkIndex))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
// Generate SDF
if (options && options->GenerateSDF)
{
stream.SetPosition(0);
if (!ModelTool::GenerateModelSDF(nullptr, &modelData, options->SDFResolution, lodCount - 1, nullptr, &stream, context.TargetAssetPath))
{
if (context.AllocateChunk(15))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[15]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
}
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
// Base
IMPORT_SETUP(SkinnedModel, SkinnedModel::SerializedVersion);
// Save skinned model header
MemoryWriteStream stream(4096);
if (modelData.Pack2SkinnedModelHeader(&stream))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
// Pack model LODs data
const auto lodCount = modelData.GetLODsCount();
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
{
stream.SetPosition(0);
// Mesh Data Version
stream.WriteByte(1);
// Pack meshes
auto& meshes = modelData.LODs[lodIndex].Meshes;
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
{
if (meshes[meshIndex]->Pack2SkinnedModel(&stream))
{
LOG(Warning, "Cannot pack mesh.");
return CreateAssetResult::Error;
}
}
const int32 chunkIndex = lodIndex + 1;
if (context.AllocateChunk(chunkIndex))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
return CreateAssetResult::Ok;
}
CreateAssetResult ImportModelFile::ImportAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
// Base
IMPORT_SETUP(Animation, Animation::SerializedVersion);
// Save animation data
MemoryWriteStream stream(8182);
if (modelData.Pack2AnimationHeader(&stream))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
return CreateAssetResult::Ok;
}
#endif

View File

@@ -1,54 +0,0 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Content/Assets/Model.h"
#include "Engine/Tools/ModelTool/ModelTool.h"
/// <summary>
/// Enable/disable caching model import options
/// </summary>
#define IMPORT_MODEL_CACHE_OPTIONS 1
/// <summary>
/// Importing models utility
/// </summary>
class ImportModelFile
{
public:
typedef ModelTool::Options Options;
public:
/// <summary>
/// Tries the get model import options from the target location asset.
/// </summary>
/// <param name="path">The asset path.</param>
/// <param name="options">The options.</param>
/// <returns>True if success, otherwise false.</returns>
static bool TryGetImportOptions(String path, Options& options);
/// <summary>
/// Imports the model file.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Import(CreateAssetContext& context);
/// <summary>
/// Creates the model asset from the ModelData storage (input argument should be pointer to ModelData).
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context);
private:
static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData);
static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData);
static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData);
};
#endif

View File

@@ -8,7 +8,6 @@
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/Textures/TextureUtils.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/ContentImporters/ImportIES.h"

View File

@@ -18,7 +18,7 @@ class CreateAssetContext;
/// <summary>
/// Create/Import new asset callback result
/// </summary>
DECLARE_ENUM_7(CreateAssetResult, Ok, Abort, Error, CannotSaveFile, InvalidPath, CannotAllocateChunk, InvalidTypeID);
DECLARE_ENUM_8(CreateAssetResult, Ok, Abort, Error, CannotSaveFile, InvalidPath, CannotAllocateChunk, InvalidTypeID, Skip);
/// <summary>
/// Create/Import new asset callback function

View File

@@ -25,6 +25,19 @@ private:
int32 _capacity;
AllocationData _allocation;
FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromCount, int32 fromCapacity)
{
if IF_CONSTEXPR (AllocationType::HasSwap)
to.Swap(from);
else
{
to.Allocate(fromCapacity);
Memory::MoveItems(to.Get(), from.Get(), fromCount);
Memory::DestructItems(from.Get(), fromCount);
from.Free();
}
}
public:
/// <summary>
/// Initializes a new instance of the <see cref="Array"/> class.
@@ -134,7 +147,7 @@ public:
_capacity = other._capacity;
other._count = 0;
other._capacity = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _count, _capacity);
}
/// <summary>
@@ -191,7 +204,7 @@ public:
_capacity = other._capacity;
other._count = 0;
other._capacity = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _count, _capacity);
}
return *this;
}
@@ -713,9 +726,16 @@ public:
/// <param name="other">The other collection.</param>
void Swap(Array& other)
{
::Swap(_count, other._count);
::Swap(_capacity, other._capacity);
_allocation.Swap(other._allocation);
if IF_CONSTEXPR (AllocationType::HasSwap)
{
_allocation.Swap(other._allocation);
::Swap(_count, other._count);
::Swap(_capacity, other._capacity);
}
else
{
::Swap(other, *this);
}
}
/// <summary>
@@ -726,9 +746,7 @@ public:
T* data = _allocation.Get();
const int32 count = _count / 2;
for (int32 i = 0; i < count; i++)
{
::Swap(data[i], data[_count - i - 1]);
}
}
public:

View File

@@ -55,9 +55,7 @@ public:
for (int32 i = 0; i < obj.Count(); i++)
{
if (predicate(obj[i]))
{
return i;
}
}
return INVALID_INDEX;
}
@@ -74,9 +72,7 @@ public:
for (int32 i = 0; i < obj.Count(); i++)
{
if (predicate(obj[i]))
{
return true;
}
}
return false;
}
@@ -93,13 +89,101 @@ public:
for (int32 i = 0; i < obj.Count(); i++)
{
if (!predicate(obj[i]))
{
return false;
}
}
return true;
}
/// <summary>
/// Filters a sequence of values based on a predicate.
/// </summary>
/// <param name="obj">The target collection.</param>
/// <param name="predicate">The prediction function. Return true for elements that should be included in result list.</param>
/// <param name="result">The result list with items that passed the predicate.</param>
template<typename T, typename AllocationType>
static void Where(const Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate, Array<T, AllocationType>& result)
{
for (const T& i : obj)
{
if (predicate(i))
result.Add(i);
}
}
/// <summary>
/// Filters a sequence of values based on a predicate.
/// </summary>
/// <param name="obj">The target collection.</param>
/// <param name="predicate">The prediction function. Return true for elements that should be included in result list.</param>
/// <returns>The result list with items that passed the predicate.</returns>
template<typename T, typename AllocationType>
static Array<T, AllocationType> Where(const Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate)
{
Array<T, AllocationType> result;
Where(obj, predicate, result);
return result;
}
/// <summary>
/// Projects each element of a sequence into a new form.
/// </summary>
/// <param name="obj">The target collection.</param>
/// <param name="selector">A transform function to apply to each source element; the second parameter of the function represents the index of the source element.</param>
/// <param name="result">The result list whose elements are the result of invoking the transform function on each element of source.</param>
template<typename TResult, typename TSource, typename AllocationType>
static void Select(const Array<TSource, AllocationType>& obj, const Function<TResult(const TSource&)>& selector, Array<TResult, AllocationType>& result)
{
for (const TSource& i : obj)
result.Add(MoveTemp(selector(i)));
}
/// <summary>
/// Projects each element of a sequence into a new form.
/// </summary>
/// <param name="obj">The target collection.</param>
/// <param name="selector">A transform function to apply to each source element; the second parameter of the function represents the index of the source element.</param>
/// <returns>The result list whose elements are the result of invoking the transform function on each element of source.</returns>
template<typename TResult, typename TSource, typename AllocationType>
static Array<TResult, AllocationType> Select(const Array<TSource, AllocationType>& obj, const Function<TResult(const TSource&)>& selector)
{
Array<TResult, AllocationType> result;
Select(obj, selector, result);
return result;
}
/// <summary>
/// Removes all the elements that match the conditions defined by the specified predicate.
/// </summary>
/// <param name="obj">The target collection to modify.</param>
/// <param name="predicate">A transform function that defines the conditions of the elements to remove.</param>
template<typename T, typename AllocationType>
static void RemoveAll(Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate)
{
for (int32 i = obj.Count() - 1; i >= 0; i--)
{
if (predicate(obj[i]))
obj.RemoveAtKeepOrder(i);
}
}
/// <summary>
/// Removes all the elements that match the conditions defined by the specified predicate.
/// </summary>
/// <param name="obj">The target collection to process.</param>
/// <param name="predicate">A transform function that defines the conditions of the elements to remove.</param>
/// <returns>The result list whose elements are the result of invoking the transform function on each element of source.</returns>
template<typename T, typename AllocationType>
static Array<T, AllocationType> RemoveAll(const Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate)
{
Array<T, AllocationType> result;
for (const T& i : obj)
{
if (!predicate(i))
result.Ass(i);
}
return result;
}
/// <summary>
/// Groups the elements of a sequence according to a specified key selector function.
/// </summary>
@@ -109,7 +193,7 @@ public:
template<typename TSource, typename TKey, typename AllocationType>
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));
Dictionary<TKey, IGrouping<TKey, TSource>> data;
for (int32 i = 0; i < obj.Count(); i++)
{
const TKey key = keySelector(obj[i]);

View File

@@ -22,6 +22,16 @@ private:
int32 _capacity;
AllocationData _allocation;
FORCE_INLINE static int32 ToItemCount(int32 size)
{
return Math::DivideAndRoundUp<int32>(size, sizeof(ItemType));
}
FORCE_INLINE static int32 ToItemCapacity(int32 size)
{
return Math::Max<int32>(Math::DivideAndRoundUp<int32>(size, sizeof(ItemType)), 1);
}
public:
/// <summary>
/// Initializes a new instance of the <see cref="BitArray"/> class.
@@ -41,7 +51,7 @@ public:
, _capacity(capacity)
{
if (capacity > 0)
_allocation.Allocate(Math::Max<ItemType>(capacity / sizeof(ItemType), 1));
_allocation.Allocate(ToItemCapacity(capacity));
}
/// <summary>
@@ -53,7 +63,7 @@ public:
_count = _capacity = other.Count();
if (_capacity > 0)
{
const uint64 itemsCapacity = Math::Max<ItemType>(_capacity / sizeof(ItemType), 1);
const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -69,7 +79,7 @@ public:
_count = _capacity = other.Count();
if (_capacity > 0)
{
const uint64 itemsCapacity = Math::Max<ItemType>(_capacity / sizeof(ItemType), 1);
const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -101,7 +111,7 @@ public:
{
_allocation.Free();
_capacity = other._count;
const uint64 itemsCapacity = Math::Max<ItemType>(_capacity / sizeof(ItemType), 1);
const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -246,7 +256,7 @@ public:
return;
ASSERT(capacity >= 0);
const int32 count = preserveContents ? (_count < capacity ? _count : capacity) : 0;
_allocation.Relocate(Math::Max<ItemType>(capacity / sizeof(ItemType), 1), _count / sizeof(ItemType), count / sizeof(ItemType));
_allocation.Relocate(ToItemCapacity(capacity), ToItemCount(_count), ToItemCount(count));
_capacity = capacity;
_count = count;
}
@@ -272,7 +282,7 @@ public:
{
if (_capacity < minCapacity)
{
const int32 capacity = _allocation.CalculateCapacityGrow(Math::Max<int32>(_capacity / sizeof(ItemType), 1), minCapacity);
const int32 capacity = _allocation.CalculateCapacityGrow(ToItemCapacity(_capacity), minCapacity);
SetCapacity(capacity, preserveContents);
}
}
@@ -284,7 +294,7 @@ public:
void SetAll(const bool value)
{
if (_count != 0)
Platform::MemorySet(_allocation.Get(), Math::Max<ItemType>(_count / sizeof(ItemType), 1), value ? MAX_int32 : 0);
Platform::MemorySet(_allocation.Get(), ToItemCount(_count) * sizeof(ItemType), value ? MAX_uint32 : 0);
}
/// <summary>

View File

@@ -100,7 +100,7 @@ public:
int32 _chunkIndex;
int32 _index;
Iterator(ChunkedArray const* collection, const int32 index)
Iterator(const ChunkedArray* collection, const int32 index)
: _collection(const_cast<ChunkedArray*>(collection))
, _chunkIndex(index / ChunkSize)
, _index(index % ChunkSize)
@@ -122,29 +122,29 @@ public:
{
}
public:
FORCE_INLINE ChunkedArray* GetChunkedArray() const
Iterator(Iterator&& i)
: _collection(i._collection)
, _chunkIndex(i._chunkIndex)
, _index(i._index)
{
return _collection;
}
public:
FORCE_INLINE int32 Index() const
{
return _chunkIndex * ChunkSize + _index;
}
public:
bool IsEnd() const
FORCE_INLINE bool IsEnd() const
{
return Index() == _collection->Count();
return (_chunkIndex * ChunkSize + _index) == _collection->_count;
}
bool IsNotEnd() const
FORCE_INLINE bool IsNotEnd() const
{
return Index() != _collection->Count();
return (_chunkIndex * ChunkSize + _index) != _collection->_count;
}
public:
FORCE_INLINE T& operator*() const
{
return _collection->_chunks[_chunkIndex]->At(_index);
@@ -155,7 +155,6 @@ public:
return &_collection->_chunks[_chunkIndex]->At(_index);
}
public:
FORCE_INLINE bool operator==(const Iterator& v) const
{
return _collection == v._collection && _chunkIndex == v._chunkIndex && _index == v._index;
@@ -166,17 +165,22 @@ public:
return _collection != v._collection || _chunkIndex != v._chunkIndex || _index != v._index;
}
public:
Iterator& operator=(const Iterator& v)
{
_collection = v._collection;
_chunkIndex = v._chunkIndex;
_index = v._index;
return *this;
}
Iterator& operator++()
{
// Check if it is not at end
const int32 end = _collection->Count();
if (Index() != end)
if ((_chunkIndex * ChunkSize + _index) != _collection->_count)
{
// Move forward within chunk
_index++;
// Check if need to change chunk
if (_index == ChunkSize && _chunkIndex < _collection->_chunks.Count() - 1)
{
// Move to next chunk
@@ -189,9 +193,9 @@ public:
Iterator operator++(int)
{
Iterator temp = *this;
++temp;
return temp;
Iterator i = *this;
++i;
return i;
}
Iterator& operator--()
@@ -199,7 +203,6 @@ public:
// Check if it's not at beginning
if (_index != 0 || _chunkIndex != 0)
{
// Check if need to change chunk
if (_index == 0)
{
// Move to previous chunk
@@ -217,9 +220,9 @@ public:
Iterator operator--(int)
{
Iterator temp = *this;
--temp;
return temp;
Iterator i = *this;
--i;
return i;
}
};
@@ -294,7 +297,7 @@ public:
{
if (IsEmpty())
return;
ASSERT(i.GetChunkedArray() == this);
ASSERT(i._collection == this);
ASSERT(i._chunkIndex < _chunks.Count() && i._index < ChunkSize);
ASSERT(i.Index() < Count());
@@ -432,11 +435,31 @@ public:
Iterator End() const
{
return Iterator(this, Count());
return Iterator(this, _count);
}
Iterator IteratorAt(int32 index) const
{
return Iterator(this, index);
}
FORCE_INLINE Iterator begin()
{
return Iterator(this, 0);
}
FORCE_INLINE Iterator end()
{
return Iterator(this, _count);
}
FORCE_INLINE const Iterator begin() const
{
return Iterator(this, 0);
}
FORCE_INLINE const Iterator end() const
{
return Iterator(this, _count);
}
};

View File

@@ -2,13 +2,26 @@
#pragma once
/// <summary>
/// Default capacity for the dictionaries (amount of space for the elements)
/// </summary>
#define DICTIONARY_DEFAULT_CAPACITY 256
#include "Engine/Platform/Defines.h"
/// <summary>
/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size)
/// Default capacity for the dictionaries (amount of space for the elements).
/// </summary>
#ifndef DICTIONARY_DEFAULT_CAPACITY
#if PLATFORM_DESKTOP
#define DICTIONARY_DEFAULT_CAPACITY 256
#else
#define DICTIONARY_DEFAULT_CAPACITY 64
#endif
#endif
/// <summary>
/// Default slack space divider for the dictionaries.
/// </summary>
#define DICTIONARY_DEFAULT_SLACK_SCALE 3
/// <summary>
/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size).
/// </summary>
#define DICTIONARY_PROB_FUNC(size, numChecks) (numChecks)
//#define DICTIONARY_PROB_FUNC(size, numChecks) (1)

View File

@@ -40,7 +40,7 @@ public:
private:
State _state;
void Free()
FORCE_INLINE void Free()
{
if (_state == Occupied)
{
@@ -50,7 +50,7 @@ public:
_state = Empty;
}
void Delete()
FORCE_INLINE void Delete()
{
_state = Deleted;
Memory::DestructItem(&Key);
@@ -58,7 +58,7 @@ public:
}
template<typename KeyComparableType>
void Occupy(const KeyComparableType& key)
FORCE_INLINE void Occupy(const KeyComparableType& key)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::ConstructItem(&Value);
@@ -66,7 +66,7 @@ public:
}
template<typename KeyComparableType>
void Occupy(const KeyComparableType& key, const ValueType& value)
FORCE_INLINE void Occupy(const KeyComparableType& key, const ValueType& value)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::ConstructItems(&Value, &value, 1);
@@ -74,7 +74,7 @@ public:
}
template<typename KeyComparableType>
void Occupy(const KeyComparableType& key, ValueType&& value)
FORCE_INLINE void Occupy(const KeyComparableType& key, ValueType&& value)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::MoveItems(&Value, &value, 1);
@@ -110,6 +110,33 @@ private:
int32 _size = 0;
AllocationData _allocation;
FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromSize)
{
if IF_CONSTEXPR (AllocationType::HasSwap)
to.Swap(from);
else
{
to.Allocate(fromSize);
Bucket* toData = to.Get();
Bucket* fromData = from.Get();
for (int32 i = 0; i < fromSize; i++)
{
Bucket& fromBucket = fromData[i];
if (fromBucket.IsOccupied())
{
Bucket& toBucket = toData[i];
Memory::MoveItems(&toBucket.Key, &fromBucket.Key, 1);
Memory::MoveItems(&toBucket.Value, &fromBucket.Value, 1);
toBucket._state = Bucket::Occupied;
Memory::DestructItem(&fromBucket.Key);
Memory::DestructItem(&fromBucket.Value);
fromBucket._state = Bucket::Empty;
}
}
from.Free();
}
}
public:
/// <summary>
/// Initializes a new instance of the <see cref="Dictionary"/> class.
@@ -132,9 +159,6 @@ public:
/// </summary>
/// <param name="other">The other collection to move.</param>
Dictionary(Dictionary&& other) noexcept
: _elementsCount(other._elementsCount)
, _deletedCount(other._deletedCount)
, _size(other._size)
{
_elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
@@ -142,7 +166,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _size);
}
/// <summary>
@@ -183,7 +207,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _size);
}
return *this;
}
@@ -375,8 +399,12 @@ public:
template<typename KeyComparableType>
ValueType& At(const KeyComparableType& key)
{
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
Compact();
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity(_elementsCount + _deletedCount + 1);
EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
// Find location of the item or place to insert it
FindPositionResult pos;
@@ -388,9 +416,9 @@ public:
// Insert
ASSERT(pos.FreeSlotIndex != -1);
_elementsCount++;
Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex];
bucket.Occupy(key);
_elementsCount++;
return bucket.Value;
}
@@ -493,7 +521,7 @@ public:
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
{
if (i->Value)
Delete(i->Value);
::Delete(i->Value);
}
Clear();
}
@@ -509,7 +537,7 @@ public:
return;
ASSERT(capacity >= 0);
AllocationData oldAllocation;
oldAllocation.Swap(_allocation);
MoveToEmpty(oldAllocation, _allocation, _size);
const int32 oldSize = _size;
const int32 oldElementsCount = _elementsCount;
_deletedCount = _elementsCount = 0;
@@ -533,13 +561,22 @@ public:
}
_size = capacity;
Bucket* oldData = oldAllocation.Get();
if (oldElementsCount != 0 && preserveContents)
if (oldElementsCount != 0 && capacity != 0 && preserveContents)
{
// TODO; move keys and values on realloc
FindPositionResult pos;
for (int32 i = 0; i < oldSize; i++)
{
if (oldData[i].IsOccupied())
Add(oldData[i].Key, MoveTemp(oldData[i].Value));
Bucket& oldBucket = oldData[i];
if (oldBucket.IsOccupied())
{
FindPosition(oldBucket.Key, pos);
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1);
Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1);
bucket->_state = Bucket::Occupied;
_elementsCount++;
}
}
}
if (oldElementsCount != 0)
@@ -558,9 +595,9 @@ public:
{
if (_size >= minCapacity)
return;
if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
minCapacity = DICTIONARY_DEFAULT_CAPACITY;
const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
if (capacity < DICTIONARY_DEFAULT_CAPACITY)
capacity = DICTIONARY_DEFAULT_CAPACITY;
SetCapacity(capacity, preserveContents);
}
@@ -570,10 +607,17 @@ public:
/// <param name="other">The other collection.</param>
void Swap(Dictionary& other)
{
::Swap(_elementsCount, other._elementsCount);
::Swap(_deletedCount, other._deletedCount);
::Swap(_size, other._size);
_allocation.Swap(other._allocation);
if IF_CONSTEXPR (AllocationType::HasSwap)
{
::Swap(_elementsCount, other._elementsCount);
::Swap(_deletedCount, other._deletedCount);
::Swap(_size, other._size);
_allocation.Swap(other._allocation);
}
else
{
::Swap(other, *this);
}
}
public:
@@ -584,24 +628,10 @@ public:
/// <param name="value">The value.</param>
/// <returns>Weak reference to the stored bucket.</returns>
template<typename KeyComparableType>
Bucket* Add(const KeyComparableType& key, const ValueType& value)
FORCE_INLINE Bucket* Add(const KeyComparableType& key, const ValueType& value)
{
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity(_elementsCount + _deletedCount + 1);
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(key, pos);
// Ensure key is unknown
ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
// Insert
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Bucket* bucket = OnAdd(key);
bucket->Occupy(key, value);
_elementsCount++;
return bucket;
}
@@ -612,24 +642,10 @@ public:
/// <param name="value">The value.</param>
/// <returns>Weak reference to the stored bucket.</returns>
template<typename KeyComparableType>
Bucket* Add(const KeyComparableType& key, ValueType&& value)
FORCE_INLINE Bucket* Add(const KeyComparableType& key, ValueType&& value)
{
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity(_elementsCount + _deletedCount + 1);
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(key, pos);
// Ensure key is unknown
ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
// Insert
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Bucket* bucket = OnAdd(key);
bucket->Occupy(key, MoveTemp(value));
_elementsCount++;
return bucket;
}
@@ -851,7 +867,7 @@ public:
return Iterator(this, _size);
}
protected:
private:
/// <summary>
/// The result container of the dictionary item lookup searching.
/// </summary>
@@ -911,4 +927,66 @@ protected:
result.ObjectIndex = -1;
result.FreeSlotIndex = insertPos;
}
template<typename KeyComparableType>
Bucket* OnAdd(const KeyComparableType& key)
{
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
Compact();
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(key, pos);
// Ensure key is unknown
ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
// Insert
ASSERT(pos.FreeSlotIndex != -1);
_elementsCount++;
return &_allocation.Get()[pos.FreeSlotIndex];
}
void Compact()
{
if (_elementsCount == 0)
{
// Fast path if it's empty
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i]._state = Bucket::Empty;
}
else
{
// Rebuild entire table completely
AllocationData oldAllocation;
MoveToEmpty(oldAllocation, _allocation, _size);
_allocation.Allocate(_size);
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i]._state = Bucket::Empty;
Bucket* oldData = oldAllocation.Get();
FindPositionResult pos;
for (int32 i = 0; i < _size; i++)
{
Bucket& oldBucket = oldData[i];
if (oldBucket.IsOccupied())
{
FindPosition(oldBucket.Key, pos);
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1);
Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1);
bucket->_state = Bucket::Occupied;
}
}
for (int32 i = 0; i < _size; i++)
oldData[i].Free();
}
_deletedCount = 0;
}
};

View File

@@ -37,26 +37,33 @@ public:
private:
State _state;
void Free()
FORCE_INLINE void Free()
{
if (_state == Occupied)
Memory::DestructItem(&Item);
_state = Empty;
}
void Delete()
FORCE_INLINE void Delete()
{
_state = Deleted;
Memory::DestructItem(&Item);
}
template<typename ItemType>
void Occupy(const ItemType& item)
FORCE_INLINE void Occupy(const ItemType& item)
{
Memory::ConstructItems(&Item, &item, 1);
_state = Occupied;
}
template<typename ItemType>
FORCE_INLINE void Occupy(ItemType& item)
{
Memory::MoveItems(&Item, &item, 1);
_state = Occupied;
}
FORCE_INLINE bool IsEmpty() const
{
return _state == Empty;
@@ -86,6 +93,31 @@ private:
int32 _size = 0;
AllocationData _allocation;
FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromSize)
{
if IF_CONSTEXPR (AllocationType::HasSwap)
to.Swap(from);
else
{
to.Allocate(fromSize);
Bucket* toData = to.Get();
Bucket* fromData = from.Get();
for (int32 i = 0; i < fromSize; i++)
{
Bucket& fromBucket = fromData[i];
if (fromBucket.IsOccupied())
{
Bucket& toBucket = toData[i];
Memory::MoveItems(&toBucket.Item, &fromBucket.Item, 1);
toBucket._state = Bucket::Occupied;
Memory::DestructItem(&fromBucket.Item);
fromBucket._state = Bucket::Empty;
}
}
from.Free();
}
}
public:
/// <summary>
/// Initializes a new instance of the <see cref="HashSet"/> class.
@@ -108,9 +140,6 @@ public:
/// </summary>
/// <param name="other">The other collection to move.</param>
HashSet(HashSet&& other) noexcept
: _elementsCount(other._elementsCount)
, _deletedCount(other._deletedCount)
, _size(other._size)
{
_elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
@@ -118,7 +147,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _size);
}
/// <summary>
@@ -159,7 +188,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _size);
}
return *this;
}
@@ -169,7 +198,7 @@ public:
/// </summary>
~HashSet()
{
SetCapacity(0, false);
Clear();
}
public:
@@ -216,6 +245,7 @@ public:
HashSet* _collection;
int32 _index;
public:
Iterator(HashSet* collection, const int32 index)
: _collection(collection)
, _index(index)
@@ -228,7 +258,12 @@ public:
{
}
public:
Iterator()
: _collection(nullptr)
, _index(-1)
{
}
Iterator(const Iterator& i)
: _collection(i._collection)
, _index(i._index)
@@ -242,6 +277,11 @@ public:
}
public:
FORCE_INLINE int32 Index() const
{
return _index;
}
FORCE_INLINE bool IsEnd() const
{
return _index == _collection->_size;
@@ -374,7 +414,7 @@ public:
return;
ASSERT(capacity >= 0);
AllocationData oldAllocation;
oldAllocation.Swap(_allocation);
MoveToEmpty(oldAllocation, _allocation, _size);
const int32 oldSize = _size;
const int32 oldElementsCount = _elementsCount;
_deletedCount = _elementsCount = 0;
@@ -398,13 +438,21 @@ public:
}
_size = capacity;
Bucket* oldData = oldAllocation.Get();
if (oldElementsCount != 0 && preserveContents)
if (oldElementsCount != 0 && capacity != 0 && preserveContents)
{
// TODO; move keys and values on realloc
FindPositionResult pos;
for (int32 i = 0; i < oldSize; i++)
{
if (oldData[i].IsOccupied())
Add(oldData[i].Item);
Bucket& oldBucket = oldData[i];
if (oldBucket.IsOccupied())
{
FindPosition(oldBucket.Item, pos);
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1);
bucket->_state = Bucket::Occupied;
_elementsCount++;
}
}
}
if (oldElementsCount != 0)
@@ -421,14 +469,35 @@ public:
/// <param name="preserveContents">True if preserve collection data when changing its size, otherwise collection after resize will be empty.</param>
void EnsureCapacity(int32 minCapacity, bool preserveContents = true)
{
if (Capacity() >= minCapacity)
if (_size >= minCapacity)
return;
if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
minCapacity = DICTIONARY_DEFAULT_CAPACITY;
const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
if (capacity < DICTIONARY_DEFAULT_CAPACITY)
capacity = DICTIONARY_DEFAULT_CAPACITY;
SetCapacity(capacity, preserveContents);
}
/// <summary>
/// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange.
/// </summary>
/// <param name="other">The other collection.</param>
void Swap(HashSet& other)
{
if IF_CONSTEXPR (AllocationType::HasSwap)
{
::Swap(_elementsCount, other._elementsCount);
::Swap(_deletedCount, other._deletedCount);
::Swap(_size, other._size);
_allocation.Swap(other._allocation);
}
else
{
HashSet tmp = MoveTemp(other);
other = *this;
*this = MoveTemp(tmp);
}
}
public:
/// <summary>
/// Add element to the collection.
@@ -438,24 +507,23 @@ public:
template<typename ItemType>
bool Add(const ItemType& item)
{
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity(_elementsCount + _deletedCount + 1);
Bucket* bucket = OnAdd(item);
if (bucket)
bucket->Occupy(item);
return bucket != nullptr;
}
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(item, pos);
// Check if object has been already added
if (pos.ObjectIndex != -1)
return false;
// Insert
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
bucket->Occupy(item);
_elementsCount++;
return true;
/// <summary>
/// Add element to the collection.
/// </summary>
/// <param name="item">The element to add to the set.</param>
/// <returns>True if element has been added to the collection, otherwise false if the element is already present.</returns>
bool Add(T&& item)
{
Bucket* bucket = OnAdd(item);
if (bucket)
bucket->Occupy(MoveTemp(item));
return bucket != nullptr;
}
/// <summary>
@@ -593,7 +661,7 @@ public:
return Iterator(this, _size);
}
protected:
private:
/// <summary>
/// The result container of the set item lookup searching.
/// </summary>
@@ -654,4 +722,66 @@ protected:
result.ObjectIndex = -1;
result.FreeSlotIndex = insertPos;
}
template<typename ItemType>
Bucket* OnAdd(const ItemType& key)
{
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
Compact();
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(key, pos);
// Check if object has been already added
if (pos.ObjectIndex != -1)
return nullptr;
// Insert
ASSERT(pos.FreeSlotIndex != -1);
_elementsCount++;
return &_allocation.Get()[pos.FreeSlotIndex];
}
void Compact()
{
if (_elementsCount == 0)
{
// Fast path if it's empty
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i]._state = Bucket::Empty;
}
else
{
// Rebuild entire table completely
AllocationData oldAllocation;
MoveToEmpty(oldAllocation, _allocation, _size);
_allocation.Allocate(_size);
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i]._state = Bucket::Empty;
Bucket* oldData = oldAllocation.Get();
FindPositionResult pos;
for (int32 i = 0; i < _size; i++)
{
Bucket& oldBucket = oldData[i];
if (oldBucket.IsOccupied())
{
FindPosition(oldBucket.Item, pos);
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1);
bucket->_state = Bucket::Occupied;
}
}
for (int32 i = 0; i < _size; i++)
oldData[i].Free();
}
_deletedCount = 0;
}
};

View File

@@ -45,6 +45,16 @@ public:
};
public:
/// <summary>
/// Sorts the linear data array using Quick Sort algorithm (non recursive version, uses temporary stack collection).
/// </summary>
/// <param name="data">The data container.</param>
template<typename T, typename AllocationType = HeapAllocation>
FORCE_INLINE static void QuickSort(Array<T, AllocationType>& data)
{
QuickSort(data.Get(), data.Count());
}
/// <summary>
/// Sorts the linear data array using Quick Sort algorithm (non recursive version, uses temporary stack collection).
/// </summary>

View File

@@ -93,3 +93,10 @@
#endif
#define PACK_STRUCT(__Declaration__) PACK_BEGIN() __Declaration__ PACK_END()
// C++ 17
#if __cplusplus >= 201703L
#define IF_CONSTEXPR constexpr
#else
#define IF_CONSTEXPR
#endif

View File

@@ -3,7 +3,7 @@
#pragma once
#include "Engine/Core/Config/Settings.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/SerializationFwd.h"
/// <summary>
/// Specifies the display mode of a game window.

View File

@@ -226,7 +226,7 @@ public:
/// <returns>Function result</returns>
FORCE_INLINE ReturnType operator()(Params... params) const
{
ASSERT(_function);
ASSERT_LOW_LAYER(_function);
return _function(_callee, Forward<Params>(params)...);
}
@@ -289,8 +289,13 @@ protected:
intptr volatile _ptr = 0;
intptr volatile _size = 0;
#else
HashSet<FunctionType>* _functions = nullptr;
CriticalSection* _locker = nullptr;
struct Data
{
HashSet<FunctionType> Functions;
CriticalSection Locker;
};
// Holds pointer to Data with Functions and Locker. Thread-safe access via atomic operations.
intptr volatile _data = 0;
#endif
typedef void (*StubSignature)(void*, Params...);
@@ -314,15 +319,12 @@ public:
_ptr = (intptr)newBindings;
_size = newSize;
#else
if (other._functions == nullptr)
Data* otherData = (Data*)Platform::AtomicRead(&_data);
if (otherData == nullptr)
return;
_functions = New<HashSet<FunctionType>>(*other._functions);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
{
if (i->Item._function && i->Item._lambda)
i->Item.LambdaCtor();
}
_locker = other._locker;
ScopeLock lock(otherData->Locker);
for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i)
Bind(i->Item);
#endif
}
@@ -334,10 +336,8 @@ public:
other._ptr = 0;
other._size = 0;
#else
_functions = other._functions;
_locker = other._locker;
other._functions = nullptr;
other._locker = nullptr;
_data = other._data;
other._data = 0;
#endif
}
@@ -356,20 +356,11 @@ public:
Allocator::Free((void*)_ptr);
}
#else
if (_locker != nullptr)
Data* data = (Data*)_data;
if (data)
{
Allocator::Free(_locker);
_locker = nullptr;
}
if (_functions != nullptr)
{
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
{
if (i->Item._lambda)
i->Item.LambdaCtor();
}
Allocator::Free(_functions);
_functions = nullptr;
_data = 0;
Delete(data);
}
#endif
}
@@ -385,8 +376,13 @@ public:
for (intptr i = 0; i < size; i++)
Bind(bindings[i]);
#else
for (auto i = other._functions->Begin(); i.IsNotEnd(); ++i)
Bind(i->Item);
Data* otherData = (Data*)Platform::AtomicRead(&_data);
if (otherData != nullptr)
{
ScopeLock lock(otherData->Locker);
for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i)
Bind(i->Item);
}
#endif
}
return *this;
@@ -402,10 +398,8 @@ public:
other._ptr = 0;
other._size = 0;
#else
_functions = other._functions;
_locker = other._locker;
other._functions = nullptr;
other._locker = nullptr;
_data = other._data;
other._data = 0;
#endif
}
return *this;
@@ -507,12 +501,20 @@ public:
Allocator::Free(bindings);
}
#else
if (_locker == nullptr)
_locker = New<CriticalSection>();
ScopeLock lock(*_locker);
if (_functions == nullptr)
_functions = New<HashSet<FunctionType>>(32);
_functions->Add(f);
Data* data = (Data*)Platform::AtomicRead(&_data);
while (!data)
{
Data* newData = New<Data>();
Data* oldData = (Data*)Platform::InterlockedCompareExchange(&_data, (intptr)newData, (intptr)data);
if (oldData != data)
{
// Other thread already set the new data so free it and try again
Delete(newData);
}
data = (Data*)Platform::AtomicRead(&_data);
}
ScopeLock lock(data->Locker);
data->Functions.Add(f);
#endif
}
@@ -568,13 +570,22 @@ public:
}
}
#else
if (_locker == nullptr)
_locker = New<CriticalSection>();
ScopeLock lock(*_locker);
if (_functions && _functions->Contains(f))
return;
Data* data = (Data*)Platform::AtomicRead(&_data);
if (data)
{
data->Locker.Lock();
if (data->Functions.Contains(f))
{
data->Locker.Unlock();
return;
}
}
#endif
Bind(f);
#if !DELEGATE_USE_ATOMIC
if (data)
data->Locker.Unlock();
#endif
}
/// <summary>
@@ -583,18 +594,9 @@ public:
template<void(*Method)(Params...)>
void Unbind()
{
#if DELEGATE_USE_ATOMIC
FunctionType f;
f.template Bind<Method>();
Unbind(f);
#else
if (_functions == nullptr)
return;
FunctionType f;
f.template Bind<Method>();
ScopeLock lock(*_locker);
_functions->Remove(f);
#endif
}
/// <summary>
@@ -604,18 +606,9 @@ public:
template<class T, void(T::*Method)(Params...)>
void Unbind(T* callee)
{
#if DELEGATE_USE_ATOMIC
FunctionType f;
f.template Bind<T, Method>(callee);
Unbind(f);
#else
if (_functions == nullptr)
return;
FunctionType f;
f.template Bind<T, Method>(callee);
ScopeLock lock(*_locker);
_functions->Remove(f);
#endif
}
/// <summary>
@@ -624,16 +617,8 @@ public:
/// <param name="method">The method.</param>
void Unbind(Signature method)
{
#if DELEGATE_USE_ATOMIC
FunctionType f(method);
Unbind(f);
#else
if (_functions == nullptr)
return;
FunctionType f(method);
ScopeLock lock(*_locker);
_functions->Remove(f);
#endif
}
/// <summary>
@@ -666,10 +651,11 @@ public:
Unbind(f);
}
#else
if (_functions == nullptr)
Data* data = (Data*)Platform::AtomicRead(&_data);
if (!data)
return;
ScopeLock lock(*_locker);
_functions->Remove(f);
ScopeLock lock(data->Locker);
data->Functions.Remove(f);
#endif
}
@@ -692,15 +678,11 @@ public:
Platform::AtomicStore((intptr volatile*)&bindings[i]._callee, 0);
}
#else
if (_functions == nullptr)
Data* data = (Data*)Platform::AtomicRead(&_data);
if (!data)
return;
ScopeLock lock(*_locker);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
{
if (i->Item._lambda)
i->Item.LambdaDtor();
}
_functions->Clear();
ScopeLock lock(data->Locker);
data->Functions.Clear();
#endif
}
@@ -710,22 +692,24 @@ public:
/// <returns>The bound functions count.</returns>
int32 Count() const
{
int32 result = 0;
#if DELEGATE_USE_ATOMIC
int32 count = 0;
const intptr size = Platform::AtomicRead((intptr volatile*)&_size);
FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr);
for (intptr i = 0; i < size; i++)
{
if (Platform::AtomicRead((intptr volatile*)&bindings[i]._function) != 0)
count++;
result++;
}
return count;
#else
if (_functions == nullptr)
return 0;
ScopeLock lock(*_locker);
return _functions->Count();
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (data)
{
ScopeLock lock(data->Locker);
result = data->Functions.Count();
}
#endif
return result;
}
/// <summary>
@@ -736,10 +720,14 @@ public:
#if DELEGATE_USE_ATOMIC
return (int32)Platform::AtomicRead((intptr volatile*)&_size);
#else
if (_functions == nullptr)
return 0;
ScopeLock lock(*_locker);
return _functions->Capacity();
int32 result = 0;
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (data)
{
ScopeLock lock(data->Locker);
result = data->Functions.Capacity();
}
return result;
#endif
}
@@ -759,10 +747,14 @@ public:
}
return false;
#else
if (_functions == nullptr)
return false;
ScopeLock lock(*_locker);
return _functions->Count() > 0;
bool result = false;
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (data)
{
ScopeLock lock(data->Locker);
result = data->Functions.Count() != 0;
}
return result;
#endif
}
@@ -791,18 +783,13 @@ public:
}
}
#else
if (_functions == nullptr)
return 0;
ScopeLock lock(*_locker);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (data)
{
if (i->Item._function != nullptr)
ScopeLock lock(data->Locker);
for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i)
{
buffer[count]._function = (StubSignature)i->Item._function;
buffer[count]._callee = (void*)i->Item._callee;
buffer[count]._lambda = (typename FunctionType::Lambda*)i->Item._lambda;
if (buffer[count]._lambda)
buffer[count].LambdaCtor();
new(buffer + count) FunctionType((const FunctionType&)i->Item);
count++;
}
}
@@ -828,15 +815,15 @@ public:
++bindings;
}
#else
if (_functions == nullptr)
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (!data)
return;
ScopeLock lock(*_locker);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
ScopeLock lock(data->Locker);
for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i)
{
auto function = (StubSignature)(i->Item._function);
auto callee = (void*)(i->Item._callee);
if (function != nullptr)
function(callee, Forward<Params>(params)...);
const FunctionType& item = i->Item;
ASSERT_LOW_LAYER(item._function);
item._function(item._callee, Forward<Params>(params)...);
}
#endif
}

View File

@@ -3,16 +3,15 @@
#include "Log.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Collections/Sorting.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/CriticalSection.h"
#include "Engine/Serialization/FileWriteStream.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Debug/Exceptions/Exceptions.h"
#if USE_EDITOR
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Collections/Sorting.h"
#endif
#include <iostream>
@@ -59,7 +58,7 @@ bool Log::Logger::Init()
int32 remaining = oldLogs.Count() - maxLogFiles + 1;
if (remaining > 0)
{
Sorting::QuickSort(oldLogs.Get(), oldLogs.Count());
Sorting::QuickSort(oldLogs);
// Delete the oldest logs
int32 i = 0;
@@ -210,17 +209,18 @@ void Log::Logger::ProcessLogMessage(LogType type, const StringView& msg, fmt_fla
hasWindowsNewLine |= msg.Get()[i - 1] != '\r' && msg.Get()[i] == '\n';
if (hasWindowsNewLine)
{
MemoryWriteStream msgStream(msgLength * sizeof(Char));
msgStream.WriteChar(msg.Get()[0]);
Array<Char> msgStream;
msgStream.EnsureCapacity(msgLength);
msgStream.Add(msg.Get()[0]);
for (int32 i = 1; i < msgLength; i++)
{
if (msg.Get()[i - 1] != '\r' && msg.Get()[i] == '\n')
msgStream.WriteChar(TEXT('\r'));
msgStream.WriteChar(msg.Get()[i]);
msgStream.Add(TEXT('\r'));
msgStream.Add(msg.Get()[i]);
}
msgStream.WriteChar(TEXT('\0'));
w.append((const Char*)msgStream.GetHandle(), (const Char*)msgStream.GetHandle() + msgStream.GetPosition());
//fmt_flax::format(w, TEXT("{}"), (const Char*)msgStream.GetHandle());
msgStream.Add(TEXT('\0'));
w.append(msgStream.Get(), (const Char*)(msgStream.Get() + msgStream.Count()));
//fmt_flax::format(w, TEXT("{}"), (const Char*)msgStream.Get());
return;
}
#endif

View File

@@ -728,9 +728,7 @@ namespace Math
/// <summary>
/// Returns value based on comparand. The main purpose of this function is to avoid branching based on floating point comparison which can be avoided via compiler intrinsics.
/// </summary>
/// <remarks>
/// Please note that this doesn't define what happens in the case of NaNs as there might be platform specific differences.
/// </remarks>
/// <remarks>Please note that this doesn't define what happens in the case of NaNs as there might be platform specific differences.</remarks>
/// <param name="comparand">Comparand the results are based on.</param>
/// <param name="valueGEZero">The result value if comparand >= 0.</param>
/// <param name="valueLTZero">The result value if comparand < 0.</param>
@@ -891,6 +889,18 @@ namespace Math
return Lerp<T>(a, b, alpha < 0.5f ? InterpCircularIn(0.f, 1.f, alpha * 2.f) * 0.5f : InterpCircularOut(0.f, 1.f, alpha * 2.f - 1.f) * 0.5f + 0.5f);
}
/// <summary>
/// Ping pongs the value <paramref name="t"/>, so that it is never larger than <paramref name="length"/> and never smaller than 0.
/// </summary>
/// <param name="t"></param>
/// <param name="length"></param>
/// <returns></returns>
template<class T>
static FORCE_INLINE T PingPong(const T& t, T length)
{
return length - Abs(Repeat(t, length * 2.0f) - length);
}
// Rotates position about the given axis by the given angle, in radians, and returns the offset to position
Vector3 FLAXENGINE_API RotateAboutAxis(const Vector3& normalizedRotationAxis, float angle, const Vector3& positionOnAxis, const Vector3& position);

View File

@@ -382,9 +382,8 @@ void Quaternion::GetRotationFromTo(const Float3& from, const Float3& to, Quatern
v0.Normalize();
v1.Normalize();
const float d = Float3::Dot(v0, v1);
// If dot == 1, vectors are the same
const float d = Float3::Dot(v0, v1);
if (d >= 1.0f)
{
result = Identity;

View File

@@ -1077,6 +1077,115 @@ namespace FlaxEngine
}
}
/// <summary>
/// Gets the shortest arc quaternion to rotate this vector to the destination vector.
/// </summary>
/// <param name="from">The source vector.</param>
/// <param name="to">The destination vector.</param>
/// <param name="result">The result.</param>
/// <param name="fallbackAxis">The fallback axis.</param>
public static void GetRotationFromTo(ref Float3 from, ref Float3 to, out Quaternion result, ref Float3 fallbackAxis)
{
// Based on Stan Melax's article in Game Programming Gems
Float3 v0 = from;
Float3 v1 = to;
v0.Normalize();
v1.Normalize();
// If dot == 1, vectors are the same
float d = Float3.Dot(ref v0, ref v1);
if (d >= 1.0f)
{
result = Identity;
return;
}
if (d < 1e-6f - 1.0f)
{
if (fallbackAxis != Float3.Zero)
{
// Rotate 180 degrees about the fallback axis
RotationAxis(ref fallbackAxis, Mathf.Pi, out result);
}
else
{
// Generate an axis
Float3 axis = Float3.Cross(Float3.UnitX, from);
if (axis.LengthSquared < Mathf.Epsilon) // Pick another if colinear
axis = Float3.Cross(Float3.UnitY, from);
axis.Normalize();
RotationAxis(ref axis, Mathf.Pi, out result);
}
}
else
{
float s = Mathf.Sqrt((1 + d) * 2);
float invS = 1 / s;
Float3.Cross(ref v0, ref v1, out var c);
result.X = c.X * invS;
result.Y = c.Y * invS;
result.Z = c.Z * invS;
result.W = s * 0.5f;
result.Normalize();
}
}
/// <summary>
/// Gets the shortest arc quaternion to rotate this vector to the destination vector.
/// </summary>
/// <param name="from">The source vector.</param>
/// <param name="to">The destination vector.</param>
/// <param name="fallbackAxis">The fallback axis.</param>
/// <returns>The rotation.</returns>
public static Quaternion GetRotationFromTo(Float3 from, Float3 to, Float3 fallbackAxis)
{
GetRotationFromTo(ref from, ref to, out var result, ref fallbackAxis);
return result;
}
/// <summary>
/// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized.
/// </summary>
/// <param name="from">The source vector.</param>
/// <param name="to">The destination vector.</param>
/// <param name="result">The result.</param>
public static void FindBetween(ref Float3 from, ref Float3 to, out Quaternion result)
{
// http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final
float normFromNormTo = Mathf.Sqrt(from.LengthSquared * to.LengthSquared);
if (normFromNormTo < Mathf.Epsilon)
{
result = Identity;
return;
}
float w = normFromNormTo + Float3.Dot(from, to);
if (w < 1.0-6f * normFromNormTo)
{
result = Mathf.Abs(from.X) > Mathf.Abs(from.Z)
? new Quaternion(-from.Y, from.X, 0.0f, 0.0f)
: new Quaternion(0.0f, -from.Z, from.Y, 0.0f);
}
else
{
Float3 cross = Float3.Cross(from, to);
result = new Quaternion(cross.X, cross.Y, cross.Z, w);
}
result.Normalize();
}
/// <summary>
/// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized.
/// </summary>
/// <param name="from">The source vector.</param>
/// <param name="to">The destination vector.</param>
/// <returns>The rotation.</returns>
public static Quaternion FindBetween(Float3 from, Float3 to)
{
FindBetween(ref from, ref to, out var result);
return result;
}
/// <summary>
/// Creates a left-handed spherical billboard that rotates around a specified object position.
/// </summary>

View File

@@ -13,9 +13,7 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
@@ -23,9 +21,7 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Double2Converter : TypeConverter
internal class Double2Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Double2(double.Parse(v[0], culture), double.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Double3Converter : TypeConverter
internal class Double3Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Double3(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Double4Converter : TypeConverter
internal class Double4Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Double4(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture), double.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Float2Converter : TypeConverter
internal class Float2Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Float2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Float3Converter : TypeConverter
internal class Float3Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Float3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,15 +7,13 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Float4Converter : TypeConverter
internal class VectorConverter : TypeConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
@@ -23,18 +21,32 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
internal static string[] GetParts(string str)
{
string[] v = str.Split(',');
if (v.Length == 1)
{
// When converting from ToString()
v = str.Split(' ');
for (int i = 0; i < v.Length; i++)
v[i] = v[i].Substring(v[i].IndexOf(':') + 1);
}
return v;
}
}
internal class Float4Converter : VectorConverter
{
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Float4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Int2Converter : TypeConverter
internal class Int2Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Int2(int.Parse(v[0], culture), int.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Int3Converter : TypeConverter
internal class Int3Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Int3(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Int4Converter : TypeConverter
internal class Int4Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Int4(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture), int.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class QuaternionConverter : TypeConverter
internal class QuaternionConverter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Quaternion(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Vector2Converter : TypeConverter
internal class Vector2Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Vector2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Vector3Converter : TypeConverter
internal class Vector3Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Vector3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Vector4Converter : TypeConverter
internal class Vector4Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Vector4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);

View File

@@ -15,6 +15,8 @@ const Float2 Float2::Zero(0.0f);
template<>
const Float2 Float2::One(1.0f);
template<>
const Float2 Float2::Half(0.5f);
template<>
const Float2 Float2::UnitX(1.0f, 0.0f);
template<>
const Float2 Float2::UnitY(0.0f, 1.0f);

View File

@@ -44,6 +44,9 @@ public:
// Vector with all components equal 1
static FLAXENGINE_API const Vector2Base<T> One;
// Vector with all components equal 0.5
static FLAXENGINE_API const Vector2Base<T> Half;
// Vector X=1, Y=0
static FLAXENGINE_API const Vector2Base<T> UnitX;
@@ -646,6 +649,12 @@ inline Vector2Base<T> operator/(typename TOtherFloat<T>::Type a, const Vector2Ba
return Vector2Base<T>(a) / b;
}
template<typename T>
inline uint32 GetHash(const Vector2Base<T>& key)
{
return (*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y;
}
namespace Math
{
template<typename T>

View File

@@ -977,6 +977,12 @@ inline Vector3Base<T> operator/(typename TOtherFloat<T>::Type a, const Vector3Ba
return Vector3Base<T>(a) / b;
}
template<typename T>
inline uint32 GetHash(const Vector3Base<T>& key)
{
return (((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z;
}
namespace Math
{
template<typename T>

View File

@@ -17,6 +17,8 @@ const Float4 Float4::Zero(0.0f);
template<>
const Float4 Float4::One(1.0f);
template<>
const Float4 Float4::Half(0.5f);
template<>
const Float4 Float4::UnitX(1.0f, 0.0f, 0.0f, 0.0f);
template<>
const Float4 Float4::UnitY(0.0f, 1.0f, 0.0f, 0.0f);

View File

@@ -54,6 +54,9 @@ public:
// Vector with all components equal 1
static FLAXENGINE_API const Vector4Base<T> One;
// Vector with all components equal 0.5
static FLAXENGINE_API const Vector4Base<T> Half;
// Vector X=1, Y=0, Z=0, W=0
static FLAXENGINE_API const Vector4Base<T> UnitX;
@@ -552,6 +555,12 @@ inline Vector4Base<T> operator/(typename TOtherFloat<T>::Type a, const Vector4Ba
return Vector4Base<T>(a) / b;
}
template<typename T>
inline uint32 GetHash(const Vector4Base<T>& key)
{
return (((((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z) * 397) ^*(uint32*)&key.W;
}
namespace Math
{
template<typename T>

View File

@@ -12,6 +12,8 @@ template<int Capacity>
class FixedAllocation
{
public:
enum { HasSwap = false };
template<typename T>
class Data
{
@@ -43,14 +45,14 @@ public:
return Capacity;
}
FORCE_INLINE void Allocate(uint64 capacity)
FORCE_INLINE void Allocate(int32 capacity)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(capacity <= Capacity);
#endif
}
FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(capacity <= Capacity);
@@ -61,12 +63,9 @@ public:
{
}
FORCE_INLINE void Swap(Data& other)
void Swap(Data& other)
{
byte tmp[Capacity * sizeof(T)];
Platform::MemoryCopy(tmp, _data, Capacity * sizeof(T));
Platform::MemoryCopy(_data, other._data, Capacity * sizeof(T));
Platform::MemoryCopy(other._data, tmp, Capacity * sizeof(T));
// Not supported
}
};
};
@@ -77,6 +76,8 @@ public:
class HeapAllocation
{
public:
enum { HasSwap = true };
template<typename T>
class Data
{
@@ -120,12 +121,15 @@ public:
capacity |= capacity >> 4;
capacity |= capacity >> 8;
capacity |= capacity >> 16;
capacity = (capacity + 1) * 2;
uint64 capacity64 = (uint64)(capacity + 1) * 2;
if (capacity64 > MAX_int32)
capacity64 = MAX_int32;
capacity = (int32)capacity64;
}
return capacity;
}
FORCE_INLINE void Allocate(uint64 capacity)
FORCE_INLINE void Allocate(int32 capacity)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(!_data);
@@ -137,7 +141,7 @@ public:
#endif
}
FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
T* newData = capacity != 0 ? (T*)Allocator::Allocate(capacity * sizeof(T)) : nullptr;
#if !BUILD_RELEASE
@@ -176,6 +180,8 @@ template<int Capacity, typename OtherAllocator = HeapAllocation>
class InlinedAllocation
{
public:
enum { HasSwap = false };
template<typename T>
class Data
{
@@ -210,7 +216,7 @@ public:
return minCapacity <= Capacity ? Capacity : _other.CalculateCapacityGrow(capacity, minCapacity);
}
FORCE_INLINE void Allocate(uint64 capacity)
FORCE_INLINE void Allocate(int32 capacity)
{
if (capacity > Capacity)
{
@@ -219,7 +225,7 @@ public:
}
}
FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
// Check if the new allocation will fit into inlined storage
if (capacity <= Capacity)
@@ -264,14 +270,9 @@ public:
}
}
FORCE_INLINE void Swap(Data& other)
void Swap(Data& other)
{
byte tmp[Capacity * sizeof(T)];
Platform::MemoryCopy(tmp, _data, Capacity * sizeof(T));
Platform::MemoryCopy(_data, other._data, Capacity * sizeof(T));
Platform::MemoryCopy(other._data, tmp, Capacity * sizeof(T));
::Swap(_useOther, other._useOther);
_other.Swap(other._other);
// Not supported
}
};
};

View File

@@ -5,6 +5,7 @@
#include "Collections/Dictionary.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Platform/CriticalSection.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ScriptingObject.h"

View File

@@ -304,7 +304,7 @@ template<typename T>
inline void Swap(T& a, T& b) noexcept
{
T tmp = MoveTemp(a);
a = b;
a = MoveTemp(b);
b = MoveTemp(tmp);
}

View File

@@ -12,22 +12,22 @@ const int32 CachedDaysToMonth[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273,
DateTime::DateTime(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second, int32 millisecond)
{
ASSERT_LOW_LAYER(Validate(year, month, day, hour, minute, second, millisecond));
int32 totalDays = 0;
int32 daysSum = 0;
if (month > 2 && IsLeapYear(year))
totalDays++;
daysSum++;
year--;
month--;
totalDays += year * 365 + year / 4 - year / 100 + year / 400 + CachedDaysToMonth[month] + day - 1;
Ticks = totalDays * Constants::TicksPerDay
+ hour * Constants::TicksPerHour
+ minute * Constants::TicksPerMinute
+ second * Constants::TicksPerSecond
+ millisecond * Constants::TicksPerMillisecond;
daysSum += year * 365 + year / 4 - year / 100 + year / 400 + CachedDaysToMonth[month] + day - 1;
Ticks = daysSum * TimeSpan::TicksPerDay
+ hour * TimeSpan::TicksPerHour
+ minute * TimeSpan::TicksPerMinute
+ second * TimeSpan::TicksPerSecond
+ millisecond * TimeSpan::TicksPerMillisecond;
}
DateTime DateTime::GetDate() const
{
return DateTime(Ticks - Ticks % Constants::TicksPerDay);
return DateTime(Ticks - Ticks % TimeSpan::TicksPerDay);
}
void DateTime::GetDate(int32& year, int32& month, int32& day) const
@@ -35,8 +35,7 @@ void DateTime::GetDate(int32& year, int32& month, int32& day) const
// Based on:
// Fliegel, H. F. and van Flandern, T. C.,
// Communications of the ACM, Vol. 11, No. 10 (October 1968).
int32 l = Math::FloorToInt(static_cast<float>(GetJulianDay() + 0.5)) + 68569;
int32 l = Math::FloorToInt((float)(GetDate().GetJulianDay() + 0.5)) + 68569;
const int32 n = 4 * l / 146097;
l = l - (146097 * n + 3) / 4;
int32 i = 4000 * (l + 1) / 1461001;
@@ -46,7 +45,6 @@ void DateTime::GetDate(int32& year, int32& month, int32& day) const
l = j / 11;
j = j + 2 - 12 * l;
i = 100 * (n - 49) + i + l;
year = i;
month = j;
day = k;
@@ -61,7 +59,7 @@ int32 DateTime::GetDay() const
DayOfWeek DateTime::GetDayOfWeek() const
{
return static_cast<DayOfWeek>((Ticks / Constants::TicksPerDay) % 7);
return static_cast<DayOfWeek>((Ticks / TimeSpan::TicksPerDay) % 7);
}
int32 DateTime::GetDayOfYear() const
@@ -75,7 +73,7 @@ int32 DateTime::GetDayOfYear() const
int32 DateTime::GetHour() const
{
return static_cast<int32>(Ticks / Constants::TicksPerHour % 24);
return static_cast<int32>(Ticks / TimeSpan::TicksPerHour % 24);
}
int32 DateTime::GetHour12() const
@@ -90,7 +88,7 @@ int32 DateTime::GetHour12() const
double DateTime::GetJulianDay() const
{
return 1721425.5 + static_cast<double>(Ticks) / Constants::TicksPerDay;
return 1721425.5 + static_cast<double>(Ticks) / TimeSpan::TicksPerDay;
}
double DateTime::GetModifiedJulianDay() const
@@ -100,12 +98,12 @@ double DateTime::GetModifiedJulianDay() const
int32 DateTime::GetMillisecond() const
{
return static_cast<int32>(Ticks / Constants::TicksPerMillisecond % 1000);
return static_cast<int32>(Ticks / TimeSpan::TicksPerMillisecond % 1000);
}
int32 DateTime::GetMinute() const
{
return static_cast<int32>(Ticks / Constants::TicksPerMinute % 60);
return static_cast<int32>(Ticks / TimeSpan::TicksPerMinute % 60);
}
int32 DateTime::GetMonth() const
@@ -122,12 +120,12 @@ MonthOfYear DateTime::GetMonthOfYear() const
int32 DateTime::GetSecond() const
{
return static_cast<int32>(Ticks / Constants::TicksPerSecond % 60);
return static_cast<int32>(Ticks / TimeSpan::TicksPerSecond % 60);
}
TimeSpan DateTime::GetTimeOfDay() const
{
return TimeSpan(Ticks % Constants::TicksPerDay);
return TimeSpan(Ticks % TimeSpan::TicksPerDay);
}
int32 DateTime::GetYear() const
@@ -137,11 +135,6 @@ int32 DateTime::GetYear() const
return year;
}
int32 DateTime::ToUnixTimestamp() const
{
return static_cast<int32>((Ticks - DateTime(1970, 1, 1).Ticks) / Constants::TicksPerSecond);
}
int32 DateTime::DaysInMonth(int32 year, int32 month)
{
ASSERT_LOW_LAYER((month >= 1) && (month <= 12));
@@ -155,16 +148,6 @@ int32 DateTime::DaysInYear(int32 year)
return IsLeapYear(year) ? 366 : 365;
}
DateTime DateTime::FromJulianDay(double julianDay)
{
return DateTime(static_cast<int64>((julianDay - 1721425.5) * Constants::TicksPerDay));
}
DateTime DateTime::FromUnixTimestamp(int32 unixTime)
{
return DateTime(1970, 1, 1) + TimeSpan(static_cast<int64>(unixTime) * Constants::TicksPerSecond);
}
bool DateTime::IsLeapYear(int32 year)
{
if ((year % 4) == 0)
@@ -176,7 +159,7 @@ bool DateTime::IsLeapYear(int32 year)
DateTime DateTime::MaxValue()
{
return DateTime(3652059 * Constants::TicksPerDay - 1);
return DateTime(3652059 * TimeSpan::TicksPerDay - 1);
}
DateTime DateTime::Now()

View File

@@ -199,11 +199,6 @@ public:
/// </summary>
int32 GetYear() const;
/// <summary>
/// Gets this date as the number of seconds since the Unix Epoch (January 1st of 1970).
/// </summary>
int32 ToUnixTimestamp() const;
public:
/// <summary>
/// Gets the number of days in the year and month.
@@ -220,20 +215,6 @@ public:
/// <returns>The number of days.</returns>
static int32 DaysInYear(int32 year);
/// <summary>
/// Returns the proleptic Gregorian date for the given Julian Day.
/// </summary>
/// <param name="julianDay">The Julian Day.</param>
/// <returns>Gregorian date and time.</returns>
static DateTime FromJulianDay(double julianDay);
/// <summary>
/// Returns the date from Unix time (seconds from midnight 1970-01-01).
/// </summary>
/// <param name="unixTime">The Unix time (seconds from midnight 1970-01-01).</param>
/// <returns>The Gregorian date and time.</returns>
static DateTime FromUnixTimestamp(int32 unixTime);
/// <summary>
/// Determines whether the specified year is a leap year.
/// </summary>

View File

@@ -107,6 +107,26 @@ public:
ASSERT(index >= 0 && index < _length);
return _data[index];
}
FORCE_INLINE T* begin()
{
return _data;
}
FORCE_INLINE T* end()
{
return _data + _length;
}
FORCE_INLINE const T* begin() const
{
return _data;
}
FORCE_INLINE const T* end() const
{
return _data + _length;
}
};
template<typename T>

View File

@@ -3,6 +3,7 @@
#pragma once
#include "String.h"
#include "StringView.h"
#include "Engine/Core/Collections/Array.h"
/// <summary>
@@ -138,6 +139,11 @@ public:
_data.Add(*str, str.Length());
return *this;
}
StringBuilder& Append(const StringView& str)
{
_data.Add(*str, str.Length());
return *this;
}
// Append int to the string
// @param val Value to append

View File

@@ -327,6 +327,18 @@ public:
bool operator!=(const String& other) const;
public:
using StringViewBase::StartsWith;
FORCE_INLINE bool StartsWith(const StringView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::StartsWith(prefix, searchCase);
}
using StringViewBase::EndsWith;
FORCE_INLINE bool EndsWith(const StringView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::EndsWith(suffix, searchCase);
}
/// <summary>
/// Gets the left most given number of characters.
/// </summary>
@@ -511,6 +523,18 @@ public:
bool operator!=(const StringAnsi& other) const;
public:
using StringViewBase::StartsWith;
FORCE_INLINE bool StartsWith(const StringAnsiView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::StartsWith(prefix, searchCase);
}
using StringViewBase::EndsWith;
FORCE_INLINE bool EndsWith(const StringAnsiView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::EndsWith(suffix, searchCase);
}
/// <summary>
/// Retrieves substring created from characters starting from startIndex to the String end.
/// </summary>

View File

@@ -6,38 +6,53 @@
TimeSpan TimeSpan::FromDays(double days)
{
ASSERT_LOW_LAYER((days >= MinValue().GetTotalDays()) && (days <= MaxValue().GetTotalDays()));
return TimeSpan(static_cast<int64>(days * Constants::TicksPerDay));
return TimeSpan(static_cast<int64>(days * TicksPerDay));
}
TimeSpan TimeSpan::FromHours(double hours)
{
ASSERT_LOW_LAYER((hours >= MinValue().GetTotalHours()) && (hours <= MaxValue().GetTotalHours()));
return TimeSpan(static_cast<int64>(hours * Constants::TicksPerHour));
return TimeSpan(static_cast<int64>(hours * TicksPerHour));
}
TimeSpan TimeSpan::FromMilliseconds(double milliseconds)
{
ASSERT_LOW_LAYER((milliseconds >= MinValue().GetTotalMilliseconds()) && (milliseconds <= MaxValue().GetTotalMilliseconds()));
return TimeSpan(static_cast<int64>(milliseconds * Constants::TicksPerMillisecond));
return TimeSpan(static_cast<int64>(milliseconds * TicksPerMillisecond));
}
TimeSpan TimeSpan::FromMinutes(double minutes)
{
ASSERT_LOW_LAYER((minutes >= MinValue().GetTotalMinutes()) && (minutes <= MaxValue().GetTotalMinutes()));
return TimeSpan(static_cast<int64>(minutes * Constants::TicksPerMinute));
return TimeSpan(static_cast<int64>(minutes * TicksPerMinute));
}
TimeSpan TimeSpan::FromSeconds(double seconds)
{
ASSERT_LOW_LAYER((seconds >= MinValue().GetTotalSeconds()) && (seconds <= MaxValue().GetTotalSeconds()));
return TimeSpan(static_cast<int64>(seconds * Constants::TicksPerSecond));
return TimeSpan(static_cast<int64>(seconds * TicksPerSecond));
}
TimeSpan TimeSpan::MaxValue()
{
return TimeSpan(9223372036854775807);
}
TimeSpan TimeSpan::MinValue()
{
return TimeSpan(-9223372036854775807 - 1);
}
TimeSpan TimeSpan::Zero()
{
return TimeSpan(0);
}
void TimeSpan::Set(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds)
{
const int64 totalMs = 1000 * (60 * 60 * 24 * (int64)days + 60 * 60 * (int64)hours + 60 * (int64)minutes + (int64)seconds) + (int64)milliseconds;
ASSERT_LOW_LAYER((totalMs >= MinValue().GetTotalMilliseconds()) && (totalMs <= MaxValue().GetTotalMilliseconds()));
Ticks = totalMs * Constants::TicksPerMillisecond;
Ticks = totalMs * TicksPerMillisecond;
}
String TimeSpan::ToString() const

View File

@@ -6,32 +6,30 @@
#include "Engine/Core/Formatting.h"
#include "Engine/Core/Templates.h"
namespace Constants
{
// The number of timespan ticks per day.
const int64 TicksPerDay = 864000000000;
// The number of timespan ticks per hour.
const int64 TicksPerHour = 36000000000;
// The number of timespan ticks per millisecond.
const int64 TicksPerMillisecond = 10000;
// The number of timespan ticks per minute.
const int64 TicksPerMinute = 600000000;
// The number of timespan ticks per second.
const int64 TicksPerSecond = 10000000;
// The number of timespan ticks per week.
const int64 TicksPerWeek = 6048000000000;
}
/// <summary>
/// Represents the difference between two dates and times.
/// </summary>
API_STRUCT(InBuild, Namespace="System") struct FLAXENGINE_API TimeSpan
{
public:
// The number of timespan ticks per day.
static constexpr int64 TicksPerDay = 864000000000;
// The number of timespan ticks per hour.
static constexpr int64 TicksPerHour = 36000000000;
// The number of timespan ticks per millisecond.
static constexpr int64 TicksPerMillisecond = 10000;
// The number of timespan ticks per minute.
static constexpr int64 TicksPerMinute = 600000000;
// The number of timespan ticks per second.
static constexpr int64 TicksPerSecond = 10000000;
// The number of timespan ticks per week.
static constexpr int64 TicksPerWeek = 6048000000000;
public:
/// <summary>
/// Time span in 100 nanoseconds resolution.
@@ -170,7 +168,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetDays() const
{
return (int32)(Ticks / Constants::TicksPerDay);
return (int32)(Ticks / TicksPerDay);
}
/// <summary>
@@ -186,7 +184,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetHours() const
{
return (int32)(Ticks / Constants::TicksPerHour % 24);
return (int32)(Ticks / TicksPerHour % 24);
}
/// <summary>
@@ -194,7 +192,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetMilliseconds() const
{
return (int32)(Ticks / Constants::TicksPerMillisecond % 1000);
return (int32)(Ticks / TicksPerMillisecond % 1000);
}
/// <summary>
@@ -202,7 +200,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetMinutes() const
{
return (int32)(Ticks / Constants::TicksPerMinute % 60);
return (int32)(Ticks / TicksPerMinute % 60);
}
/// <summary>
@@ -210,7 +208,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetSeconds() const
{
return (int32)(Ticks / Constants::TicksPerSecond % 60);
return (int32)(Ticks / TicksPerSecond % 60);
}
/// <summary>
@@ -218,7 +216,7 @@ public:
/// </summary>
FORCE_INLINE double GetTotalDays() const
{
return (double)Ticks / Constants::TicksPerDay;
return (double)Ticks / TicksPerDay;
}
/// <summary>
@@ -226,7 +224,7 @@ public:
/// </summary>
FORCE_INLINE double GetTotalHours() const
{
return (double)Ticks / Constants::TicksPerHour;
return (double)Ticks / TicksPerHour;
}
/// <summary>
@@ -234,7 +232,7 @@ public:
/// </summary>
FORCE_INLINE double GetTotalMilliseconds() const
{
return (double)Ticks / Constants::TicksPerMillisecond;
return (double)Ticks / TicksPerMillisecond;
}
/// <summary>
@@ -242,7 +240,7 @@ public:
/// </summary>
FORCE_INLINE double GetTotalMinutes() const
{
return (double)Ticks / Constants::TicksPerMinute;
return (double)Ticks / TicksPerMinute;
}
/// <summary>
@@ -250,7 +248,7 @@ public:
/// </summary>
FORCE_INLINE float GetTotalSeconds() const
{
return static_cast<float>(Ticks) / Constants::TicksPerSecond;
return static_cast<float>(Ticks) / TicksPerSecond;
}
public:
@@ -293,29 +291,17 @@ public:
/// <summary>
/// Returns the maximum time span value.
/// </summary>
/// <returns>The time span.</returns>
static TimeSpan MaxValue()
{
return TimeSpan(9223372036854775807);
}
static TimeSpan MaxValue();
/// <summary>
/// Returns the minimum time span value.
/// </summary>
/// <returns>The time span.</returns>
static TimeSpan MinValue()
{
return TimeSpan(-9223372036854775807 - 1);
}
static TimeSpan MinValue();
/// <summary>
/// Returns the zero time span value.
/// </summary>
/// <returns>The time span.</returns>
static TimeSpan Zero()
{
return TimeSpan(0);
}
static TimeSpan Zero();
private:
void Set(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds);

View File

@@ -139,6 +139,24 @@ VariantType::VariantType(const StringAnsiView& typeName)
return;
}
}
{
// Aliases
if (typeName == "FlaxEngine.Vector2")
{
new(this) VariantType(Vector2);
return;
}
if (typeName == "FlaxEngine.Vector3")
{
new(this) VariantType(Vector3);
return;
}
if (typeName == "FlaxEngine.Vector4")
{
new(this) VariantType(Vector4);
return;
}
}
// Check case for array
if (typeName.EndsWith(StringAnsiView("[]"), StringSearchCase::CaseSensitive))
@@ -2821,7 +2839,10 @@ void Variant::Inline()
type = VariantType::Types::Vector4;
}
if (type != VariantType::Null)
{
ASSERT(sizeof(data) >= AsBlob.Length);
Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
}
}
if (type != VariantType::Null)
{
@@ -2912,6 +2933,60 @@ void Variant::Inline()
}
}
void Variant::InvertInline()
{
byte data[sizeof(Matrix)];
switch (Type.Type)
{
case VariantType::Bool:
case VariantType::Int:
case VariantType::Uint:
case VariantType::Int64:
case VariantType::Uint64:
case VariantType::Float:
case VariantType::Double:
case VariantType::Pointer:
case VariantType::String:
case VariantType::Float2:
case VariantType::Float3:
case VariantType::Float4:
case VariantType::Color:
#if !USE_LARGE_WORLDS
case VariantType::BoundingSphere:
case VariantType::BoundingBox:
case VariantType::Ray:
#endif
case VariantType::Guid:
case VariantType::Quaternion:
case VariantType::Rectangle:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Int16:
case VariantType::Uint16:
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
static_assert(sizeof(data) >= sizeof(AsData), "Invalid memory size.");
Platform::MemoryCopy(data, AsData, sizeof(AsData));
break;
#if USE_LARGE_WORLDS
case VariantType::BoundingSphere:
case VariantType::BoundingBox:
case VariantType::Ray:
#endif
case VariantType::Transform:
case VariantType::Matrix:
ASSERT(sizeof(data) >= AsBlob.Length);
Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
break;
default:
return; // Not used
}
SetType(VariantType(VariantType::Structure, InBuiltTypesTypeNames[Type.Type]));
CopyStructure(data);
}
Variant Variant::NewValue(const StringAnsiView& typeName)
{
Variant v;
@@ -3928,15 +4003,32 @@ void Variant::CopyStructure(void* src)
{
if (AsBlob.Data && src)
{
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(StringAnsiView(Type.TypeName));
const StringAnsiView typeName(Type.TypeName);
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName);
if (typeHandle)
{
auto& type = typeHandle.GetType();
type.Struct.Copy(AsBlob.Data, src);
}
#if USE_CSHARP
else if (const auto mclass = Scripting::FindClass(typeName))
{
// Fallback to C#-only types
MCore::Thread::Attach();
if (MANAGED_GC_HANDLE && mclass->IsValueType())
{
MObject* instance = MCore::GCHandle::GetTarget(MANAGED_GC_HANDLE);
void* data = MCore::Object::Unbox(instance);
Platform::MemoryCopy(data, src, mclass->GetInstanceSize());
}
}
#endif
else
{
Platform::MemoryCopy(AsBlob.Data, src, AsBlob.Length);
if (typeName.Length() != 0)
{
LOG(Warning, "Missing scripting type \'{0}\'", String(typeName));
}
}
}
}

View File

@@ -372,6 +372,9 @@ public:
// Inlines potential value type into in-built format (eg. Vector3 stored as Structure, or String stored as ManagedObject).
void Inline();
// Inverts the inlined value from in-built format into generic storage (eg. Float3 from inlined format into Structure).
void InvertInline();
// Allocates the Variant of the specific type (eg. structure or object or value).
static Variant NewValue(const StringAnsiView& typeName);

View File

@@ -7,15 +7,15 @@ Version::Version(int32 major, int32 minor, int32 build, int32 revision)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
_build = Math::Max(build, 0);
_revision = Math::Max(revision, 0);
_build = Math::Max(build, -1);
_revision = Math::Max(revision, -1);
}
Version::Version(int32 major, int32 minor, int32 build)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
_build = Math::Max(build, 0);
_build = Math::Max(build, -1);
_revision = -1;
}

View File

@@ -51,10 +51,14 @@ namespace Utilities
int32 i = 0;
double dblSUnits = static_cast<double>(units);
for (; static_cast<uint64>(units / static_cast<double>(divider)) > 0; i++, units /= divider)
dblSUnits = units / static_cast<double>(divider);
dblSUnits = (double)units / (double)divider;
if (i >= sizes.Length())
i = 0;
return String::Format(TEXT("{0} {1}"), RoundTo2DecimalPlaces(dblSUnits), sizes[i]);
String text = String::Format(TEXT("{}"), RoundTo2DecimalPlaces(dblSUnits));
const int32 dot = text.FindLast('.');
if (dot != -1)
text = text.Left(dot + 3);
return String::Format(TEXT("{0} {1}"), text, sizes[i]);
}
// Converts size of the file (in bytes) to the best fitting string

View File

@@ -696,12 +696,15 @@ void* DebugDraw::AllocateContext()
void DebugDraw::FreeContext(void* context)
{
ASSERT(context);
Memory::DestructItem((DebugDrawContext*)context);
Allocator::Free(context);
}
void DebugDraw::UpdateContext(void* context, float deltaTime)
{
if (!context)
context = &GlobalContext;
((DebugDrawContext*)context)->DebugDrawDefault.Update(deltaTime);
((DebugDrawContext*)context)->DebugDrawDepthTest.Update(deltaTime);
}

View File

@@ -20,7 +20,6 @@
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Threading/ThreadRegistry.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/JsonAsset.h"
@@ -327,14 +326,6 @@ void Engine::OnUpdate()
// Update services
EngineService::OnUpdate();
#ifdef USE_NETCORE
// Force GC to run in background periodically to avoid large blocking collections causing hitches
if (Time::Update.TicksCount % 60 == 0)
{
MCore::GC::Collect(MCore::GC::MaxGeneration(), MGCCollectionMode::Forced, false, false);
}
#endif
}
void Engine::OnLateUpdate()
@@ -531,7 +522,13 @@ void EngineImpl::InitLog()
LOG(Info, "Compiled for Dev Environment");
#endif
LOG(Info, "Version " FLAXENGINE_VERSION_TEXT);
LOG(Info, "Compiled: {0} {1}", TEXT(__DATE__), TEXT(__TIME__));
const Char* cpp = TEXT("?");
if (__cplusplus == 202101L) cpp = TEXT("C++23");
else if (__cplusplus == 202002L) cpp = TEXT("C++20");
else if (__cplusplus == 201703L) cpp = TEXT("C++17");
else if (__cplusplus == 201402L) cpp = TEXT("C++14");
else if (__cplusplus == 201103L) cpp = TEXT("C++11");
LOG(Info, "Compiled: {0} {1} {2}", TEXT(__DATE__), TEXT(__TIME__), cpp);
#ifdef _MSC_VER
const String mcsVer = StringUtils::ToString(_MSC_FULL_VER);
LOG(Info, "Compiled with Visual C++ {0}.{1}.{2}.{3:0^2d}", mcsVer.Substring(0, 2), mcsVer.Substring(2, 2), mcsVer.Substring(4, 5), _MSC_BUILD);

View File

@@ -1022,6 +1022,8 @@ namespace FlaxEngine.Interop
pair.Value.Free();
classAttributesCacheCollectible.Clear();
FlaxEngine.Json.JsonSerializer.ResetCache();
// Unload the ALC
bool unloading = true;
scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; };
@@ -1269,6 +1271,9 @@ namespace FlaxEngine.Interop
case Type _ when type == typeof(IntPtr):
monoType = MTypes.Ptr;
break;
case Type _ when type.IsPointer:
monoType = MTypes.Ptr;
break;
case Type _ when type.IsEnum:
monoType = MTypes.Enum;
break;

View File

@@ -278,44 +278,70 @@ namespace FlaxEngine.Interop
if (typeCache.TryGetValue(typeName, out Type type))
return type;
type = Type.GetType(typeName, ResolveAssemblyByName, null);
type = Type.GetType(typeName, ResolveAssembly, null);
if (type == null)
{
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
{
type = assembly.GetType(typeName);
if (type != null)
break;
}
}
type = ResolveSlow(typeName);
if (type == null)
{
string oldTypeName = typeName;
string fullTypeName = typeName;
typeName = typeName.Substring(0, typeName.IndexOf(','));
type = Type.GetType(typeName, ResolveAssemblyByName, null);
type = Type.GetType(typeName, ResolveAssembly, null);
if (type == null)
{
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
{
type = assembly.GetType(typeName);
if (type != null)
break;
}
}
typeName = oldTypeName;
type = ResolveSlow(typeName);
typeName = fullTypeName;
}
typeCache.Add(typeName, type);
return type;
static Type ResolveSlow(string typeName)
{
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
{
var type = assembly.GetType(typeName);
if (type != null)
return type;
}
return null;
}
static Assembly ResolveAssembly(AssemblyName name) => ResolveScriptingAssemblyByName(name, allowPartial: false);
}
private static Assembly ResolveAssemblyByName(AssemblyName assemblyName)
/// <summary>Find <paramref name="assemblyName"/> among the scripting assemblies.</summary>
/// <param name="assemblyName">The name to find</param>
/// <param name="allowPartial">If true, partial names should be allowed to be resolved.</param>
/// <returns>The resolved assembly, or null if none could be found.</returns>
internal static Assembly ResolveScriptingAssemblyByName(AssemblyName assemblyName, bool allowPartial = false)
{
foreach (Assembly assembly in scriptingAssemblyLoadContext.Assemblies)
if (assembly.GetName() == assemblyName)
var lc = scriptingAssemblyLoadContext;
if (lc is null)
return null;
foreach (Assembly assembly in lc.Assemblies)
{
var curName = assembly.GetName();
if (curName == assemblyName)
return assembly;
}
if (allowPartial) // Check partial names if full name isn't found
{
string partialName = assemblyName.Name;
foreach (Assembly assembly in lc.Assemblies)
{
var curName = assembly.GetName();
if (curName.Name == partialName)
return assembly;
}
}
return null;
}
@@ -1109,7 +1135,7 @@ namespace FlaxEngine.Interop
marshallers[i](fields[i], offsets[i], ref managedValue, fieldPtr, out int fieldSize);
fieldPtr += fieldSize;
}
Assert.IsTrue((fieldPtr - nativePtr) <= Unsafe.SizeOf<T>());
//Assert.IsTrue((fieldPtr - nativePtr) <= GetTypeSize(typeof(T)));
}
internal static void ToManaged(ref T managedValue, IntPtr nativePtr, bool byRef)
@@ -1156,7 +1182,7 @@ namespace FlaxEngine.Interop
marshallers[i](fields[i], offsets[i], ref managedValue, nativePtr, out int fieldSize);
nativePtr += fieldSize;
}
Assert.IsTrue((nativePtr - fieldPtr) <= Unsafe.SizeOf<T>());
//Assert.IsTrue((nativePtr - fieldPtr) <= GetTypeSize(typeof(T)));
}
internal static void ToNative(ref T managedValue, IntPtr nativePtr)
@@ -1302,41 +1328,53 @@ namespace FlaxEngine.Interop
#if !USE_AOT
internal bool TryGetDelegate(out Invoker.MarshalAndInvokeDelegate outDeleg, out object outDelegInvoke)
{
if (invokeDelegate == null)
// Skip using in-built delegate for value types (eg. Transform) to properly handle instance value passing to method
if (invokeDelegate == null && !method.DeclaringType.IsValueType)
{
List<Type> methodTypes = new List<Type>();
if (!method.IsStatic)
methodTypes.Add(method.DeclaringType);
if (returnType != typeof(void))
methodTypes.Add(returnType);
methodTypes.AddRange(parameterTypes);
List<Type> genericParamTypes = new List<Type>();
foreach (var type in methodTypes)
// Thread-safe creation
lock (typeCache)
{
if (type.IsByRef)
genericParamTypes.Add(type.GetElementType());
else if (type.IsPointer)
genericParamTypes.Add(typeof(IntPtr));
else
genericParamTypes.Add(type);
}
string invokerTypeName = $"{typeof(Invoker).FullName}+Invoker{(method.IsStatic ? "Static" : "")}{(returnType != typeof(void) ? "Ret" : "NoRet")}{parameterTypes.Length}{(genericParamTypes.Count > 0 ? "`" + genericParamTypes.Count : "")}";
Type invokerType = Type.GetType(invokerTypeName);
if (invokerType != null)
{
if (genericParamTypes.Count != 0)
invokerType = invokerType.MakeGenericType(genericParamTypes.ToArray());
invokeDelegate = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.MarshalAndInvoke), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Invoker.MarshalAndInvokeDelegate>();
delegInvoke = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.CreateDelegate), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { method });
if (invokeDelegate == null)
{
TryCreateDelegate();
}
}
}
outDeleg = invokeDelegate;
outDelegInvoke = delegInvoke;
return outDeleg != null;
}
private void TryCreateDelegate()
{
var methodTypes = new List<Type>();
if (!method.IsStatic)
methodTypes.Add(method.DeclaringType);
if (returnType != typeof(void))
methodTypes.Add(returnType);
methodTypes.AddRange(parameterTypes);
var genericParamTypes = new List<Type>();
foreach (var type in methodTypes)
{
if (type.IsByRef)
genericParamTypes.Add(type.GetElementType());
else if (type.IsPointer)
genericParamTypes.Add(typeof(IntPtr));
else
genericParamTypes.Add(type);
}
string invokerTypeName = $"{typeof(Invoker).FullName}+Invoker{(method.IsStatic ? "Static" : "")}{(returnType != typeof(void) ? "Ret" : "NoRet")}{parameterTypes.Length}{(genericParamTypes.Count > 0 ? "`" + genericParamTypes.Count : "")}";
Type invokerType = Type.GetType(invokerTypeName);
if (invokerType != null)
{
if (genericParamTypes.Count != 0)
invokerType = invokerType.MakeGenericType(genericParamTypes.ToArray());
delegInvoke = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.CreateDelegate), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { method });
invokeDelegate = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.MarshalAndInvoke), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Invoker.MarshalAndInvokeDelegate>();
}
}
#endif
}
@@ -1553,7 +1591,7 @@ namespace FlaxEngine.Interop
private static IntPtr PinValue<T>(T value) where T : struct
{
// Store the converted value in unmanaged memory so it will not be relocated by the garbage collector.
int size = Unsafe.SizeOf<T>();
int size = GetTypeSize(typeof(T));
uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length;
ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index];
if (alloc.size < size)

View File

@@ -22,7 +22,7 @@ class ScreenService : public EngineService
{
public:
ScreenService()
: EngineService(TEXT("Screen"), 120)
: EngineService(TEXT("Screen"), 500)
{
}

View File

@@ -67,7 +67,7 @@ void Time::TickData::OnBeforeRun(float targetFps, double currentTime)
{
Time = UnscaledTime = TimeSpan::Zero();
DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero();
LastLength = static_cast<double>(DeltaTime.Ticks) / Constants::TicksPerSecond;
LastLength = static_cast<double>(DeltaTime.Ticks) / TimeSpan::TicksPerSecond;
LastBegin = currentTime - LastLength;
LastEnd = currentTime;
NextBegin = targetFps > ZeroTolerance ? LastBegin + (1.0f / targetFps) : 0.0;
@@ -76,7 +76,7 @@ void Time::TickData::OnBeforeRun(float targetFps, double currentTime)
void Time::TickData::OnReset(float targetFps, double currentTime)
{
DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero();
LastLength = static_cast<double>(DeltaTime.Ticks) / Constants::TicksPerSecond;
LastLength = static_cast<double>(DeltaTime.Ticks) / TimeSpan::TicksPerSecond;
LastBegin = currentTime - LastLength;
LastEnd = currentTime;
}
@@ -104,7 +104,7 @@ bool Time::TickData::OnTickBegin(float targetFps, float maxDeltaTime)
if (targetFps > ZeroTolerance)
{
int skip = (int)(1 + (time - NextBegin) / (1.0 / targetFps));
int skip = (int)(1 + (time - NextBegin) * targetFps);
NextBegin += (1.0 / targetFps) * skip;
}
}
@@ -160,7 +160,7 @@ bool Time::FixedStepTickData::OnTickBegin(float targetFps, float maxDeltaTime)
if (targetFps > ZeroTolerance)
{
int skip = (int)(1 + (time - NextBegin) / (1.0 / targetFps));
int skip = (int)(1 + (time - NextBegin) * targetFps);
NextBegin += (1.0 / targetFps) * skip;
}
}

View File

@@ -634,15 +634,12 @@ void Foliage::RemoveFoliageType(int32 index)
int32 Foliage::GetFoliageTypeInstancesCount(int32 index) const
{
PROFILE_CPU();
int32 result = 0;
for (auto i = Instances.Begin(); i.IsNotEnd(); i++)
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
{
if (i->Type == index)
result++;
}
return result;
}

View File

@@ -46,7 +46,7 @@ API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FoliageType : public ScriptingOb
DECLARE_SCRIPTING_TYPE_NO_SPAWN(FoliageType);
friend Foliage;
private:
int8 _isReady : 1;
uint8 _isReady : 1;
public:
/// <summary>

View File

@@ -26,12 +26,12 @@ public:
, _srcResource(src)
, _dstResource(dst)
{
_srcResource.OnUnload.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceUnload>(this);
_dstResource.OnUnload.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceUnload>(this);
_srcResource.Released.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceReleased>(this);
_dstResource.Released.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceReleased>(this);
}
private:
void OnResourceUnload(GPUResourceReference* ref)
void OnResourceReleased()
{
Cancel();
}
@@ -47,14 +47,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
if (_srcResource.IsMissing() || _dstResource.IsMissing())
if (!_srcResource || !_dstResource)
return Result::MissingResources;
context->GPU->CopyResource(_dstResource, _srcResource);
return Result::Ok;
}
void OnEnd() override
{
_srcResource.Unlink();

View File

@@ -31,12 +31,12 @@ public:
, _srcSubresource(srcSubresource)
, _dstSubresource(dstSubresource)
{
_srcResource.OnUnload.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceUnload>(this);
_dstResource.OnUnload.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceUnload>(this);
_srcResource.Released.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceReleased>(this);
_dstResource.Released.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceReleased>(this);
}
private:
void OnResourceUnload(GPUResourceReference* ref)
void OnResourceReleased()
{
Cancel();
}
@@ -52,14 +52,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
if (_srcResource.IsMissing() || _dstResource.IsMissing())
if (!_srcResource || !_dstResource)
return Result::MissingResources;
context->GPU->CopySubresource(_dstResource, _dstSubresource, _srcResource, _srcSubresource);
return Result::Ok;
}
void OnEnd() override
{
_srcResource.Unlink();

View File

@@ -31,7 +31,7 @@ public:
, _buffer(buffer)
, _offset(offset)
{
_buffer.OnUnload.Bind<GPUUploadBufferTask, &GPUUploadBufferTask::OnResourceUnload>(this);
_buffer.Released.Bind<GPUUploadBufferTask, &GPUUploadBufferTask::OnResourceReleased>(this);
if (copyData)
_data.Copy(data);
@@ -40,7 +40,7 @@ public:
}
private:
void OnResourceUnload(BufferReference* ref)
void OnResourceReleased()
{
Cancel();
}
@@ -56,14 +56,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
if (_buffer.IsMissing())
if (!_buffer)
return Result::MissingResources;
context->GPU->UpdateBuffer(_buffer, _data.Get(), _data.Length(), _offset);
return Result::Ok;
}
void OnEnd() override
{
_buffer.Unlink();

View File

@@ -35,7 +35,7 @@ public:
, _rowPitch(rowPitch)
, _slicePitch(slicePitch)
{
_texture.OnUnload.Bind<GPUUploadTextureMipTask, &GPUUploadTextureMipTask::OnResourceUnload>(this);
_texture.Released.Bind<GPUUploadTextureMipTask, &GPUUploadTextureMipTask::OnResourceReleased>(this);
if (copyData)
_data.Copy(data);
@@ -44,7 +44,7 @@ public:
}
private:
void OnResourceUnload(GPUTextureReference* ref)
void OnResourceReleased()
{
Cancel();
}

Some files were not shown because too many files have changed in this diff Show More