Merge branch 'master' into collection-ui
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -146,6 +146,7 @@ void AnimationsSystem::PostExecute(TaskGraph* graph)
|
||||
auto animatedModel = AnimationManagerInstance.UpdateList[index];
|
||||
if (CanUpdateModel(animatedModel))
|
||||
{
|
||||
animatedModel->GraphInstance.InvokeAnimEvents();
|
||||
animatedModel->OnAnimationUpdated_Sync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -225,6 +225,7 @@ bool AudioClip::ExtractDataRaw(Array<byte>& resultData, AudioDataInfo& resultDat
|
||||
|
||||
void AudioClip::CancelStreaming()
|
||||
{
|
||||
Asset::CancelStreaming();
|
||||
CancelStreamingTasks();
|
||||
}
|
||||
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,7 @@ public:
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="SoftAssetReferenceBase"/> class.
|
||||
/// </summary>
|
||||
~SoftAssetReferenceBase()
|
||||
{
|
||||
}
|
||||
~SoftAssetReferenceBase();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
743
Source/Engine/ContentImporters/ImportModel.cpp
Normal file
743
Source/Engine/ContentImporters/ImportModel.cpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -22,7 +22,7 @@ class ScreenService : public EngineService
|
||||
{
|
||||
public:
|
||||
ScreenService()
|
||||
: EngineService(TEXT("Screen"), 120)
|
||||
: EngineService(TEXT("Screen"), 500)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user