diff --git a/Source/Editor/Surface/Archetypes/Flow.cs b/Source/Editor/Surface/Archetypes/Flow.cs
index ffcfb719a..d7fc5dcc2 100644
--- a/Source/Editor/Surface/Archetypes/Flow.cs
+++ b/Source/Editor/Surface/Archetypes/Flow.cs
@@ -293,6 +293,21 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(1, "Value", true, null, 1),
}
},
+ new NodeArchetype
+ {
+ TypeID = 6,
+ Title = "Delay",
+ Description = "Delays the graph execution. If delay is 0 then it will pass though.",
+ Flags = NodeFlags.VisualScriptGraph,
+ Size = new Vector2(150, 40),
+ DefaultValues = new object[] { 1.0f },
+ Elements = new[]
+ {
+ NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 0),
+ NodeElementArchetype.Factory.Input(1, "Duration", true, typeof(float), 1, 0),
+ NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 2, true),
+ }
+ },
};
}
}
diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs
index 005af20cc..645a243f6 100644
--- a/Source/Editor/Surface/Archetypes/Tools.cs
+++ b/Source/Editor/Surface/Archetypes/Tools.cs
@@ -677,19 +677,21 @@ namespace FlaxEditor.Surface.Archetypes
///
public ThisNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
- {}
+ {
+ }
///
public override void OnLoaded()
{
base.OnLoaded();
- var vss = (VisualScriptSurface)this.Context.Surface;
- var type = TypeUtils.GetType(vss.Script.ScriptTypeName);
+
+ var surface = (VisualScriptSurface)Context.Surface;
+ var type = TypeUtils.GetType(surface.Script.ScriptTypeName);
var box = (OutputBox)GetBox(0);
box.CurrentType = type ? type : new ScriptType(typeof(VisualScript));
}
}
-
+
private class AssetReferenceNode : SurfaceNode
{
///
diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp
index 2da76a840..ea8f6ad6c 100644
--- a/Source/Engine/Content/Assets/VisualScript.cpp
+++ b/Source/Engine/Content/Assets/VisualScript.cpp
@@ -19,6 +19,7 @@
#include "Engine/Serialization/JsonWriter.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Utilities/StringConverter.h"
+#include "Engine/Threading/MainThreadTask.h"
#include "FlaxEngine.Gen.h"
namespace
@@ -83,6 +84,41 @@ VisualScriptExecutor::VisualScriptExecutor()
_perGroupProcessCall[17] = (ProcessBoxHandler)&VisualScriptExecutor::ProcessGroupFlow;
}
+void VisualScriptExecutor::Invoke(const Guid& scriptId, int32 nodeId, int32 boxId, const Guid& instanceId, Variant& result) const
+{
+ auto script = Content::Load(scriptId);
+ if (!script)
+ return;
+ const auto node = script->Graph.GetNode(nodeId);
+ if (!node)
+ return;
+ const auto box = node->GetBox(boxId);
+ if (!box)
+ return;
+ auto instance = Scripting::FindObject(instanceId);
+
+ // Add to the calling stack
+ VisualScripting::ScopeContext scope;
+ auto& stack = ThreadStacks.Get();
+ VisualScripting::StackFrame frame;
+ frame.Script = script;
+ frame.Node = node;
+ frame.Box = box;
+ frame.Instance = instance;
+ frame.PreviousFrame = stack.Stack;
+ frame.Scope = &scope;
+ stack.Stack = &frame;
+ stack.StackFramesCount++;
+
+ // Call per group custom processing event
+ const auto func = VisualScriptingExecutor._perGroupProcessCall[node->GroupID];
+ (VisualScriptingExecutor.*func)(box, node, result);
+
+ // Remove from the calling stack
+ stack.StackFramesCount--;
+ stack.Stack = frame.PreviousFrame;
+}
+
VisjectExecutor::Value VisualScriptExecutor::eatBox(Node* caller, Box* box)
{
// Check if graph is looped or is too deep
@@ -1273,6 +1309,46 @@ void VisualScriptExecutor::ProcessGroupFlow(Box* boxBase, Node* node, Value& val
}
}
break;
+ }
+ // Delay
+ case 6:
+ {
+ boxBase = node->GetBox(2);
+ if (!boxBase->HasConnection())
+ break;
+ const float duration = (float)tryGetValue(node->GetBox(1), node->Values[0]);
+ if (duration > ZeroTolerance)
+ {
+ class DelayTask : public MainThreadTask
+ {
+ public:
+ Guid Script;
+ Guid Instance;
+ int32 Node;
+ int32 Box;
+
+ protected:
+ bool Run() override
+ {
+ Variant result;
+ VisualScriptingExecutor.Invoke(Script, Node, Box, Instance, result);
+ return false;
+ }
+ };
+ const auto& stack = ThreadStacks.Get().Stack;
+ auto task = New();
+ task->Script = stack->Script->GetID();;
+ task->Instance = stack->Instance->GetID();;
+ task->Node = ((Node*)boxBase->FirstConnection()->Parent)->ID;
+ task->Box = boxBase->FirstConnection()->ID;
+ task->InitialDelay = duration;
+ task->Start();
+ }
+ else
+ {
+ eatBox(node, boxBase->FirstConnection());
+ }
+ break;
}
}
}
diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h
index 1a1201191..27ee21b18 100644
--- a/Source/Engine/Content/Assets/VisualScript.h
+++ b/Source/Engine/Content/Assets/VisualScript.h
@@ -36,6 +36,8 @@ public:
///
VisualScriptExecutor();
+ void Invoke(const Guid& scriptId, int32 nodeId, int32 boxId, const Guid& instanceId, Variant& result) const;
+
private:
Value eatBox(Node* caller, Box* box) override;
Graph* GetCurrentGraph() const override;
diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp
index bfa2338c6..020f53faa 100644
--- a/Source/Engine/Engine/Engine.cpp
+++ b/Source/Engine/Engine/Engine.cpp
@@ -288,7 +288,7 @@ void Engine::OnUpdate()
// Simulate lags
//Platform::Sleep(100);
- MainThreadTask::RunAll();
+ MainThreadTask::RunAll(Time::Update.UnscaledDeltaTime.GetTotalSeconds());
// Call event
Update();
diff --git a/Source/Engine/Threading/MainThreadTask.cpp b/Source/Engine/Threading/MainThreadTask.cpp
index e6199986c..29bd60dc3 100644
--- a/Source/Engine/Threading/MainThreadTask.cpp
+++ b/Source/Engine/Threading/MainThreadTask.cpp
@@ -1,25 +1,68 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "MainThreadTask.h"
-#include "ConcurrentTaskQueue.h"
+#include "Engine/Platform/CriticalSection.h"
#include "Engine/Profiler/ProfilerCPU.h"
-ConcurrentTaskQueue MainThreadTasks;
-
-void MainThreadTask::RunAll()
+namespace
{
- // TODO: use bulk dequeue
+ CriticalSection Locker;
+ Array Waiting;
+ Array Queue;
+}
+void MainThreadTask::RunAll(float dt)
+{
PROFILE_CPU();
-
- MainThreadTask* task;
- while (MainThreadTasks.try_dequeue(task))
+ Locker.Lock();
+ for (int32 i = Waiting.Count() - 1; i >= 0; i--)
{
- task->Execute();
+ auto task = Waiting[i];
+ task->InitialDelay -= dt;
+ if (task->InitialDelay < ZeroTolerance)
+ {
+ Waiting.RemoveAt(i);
+ Queue.Add(task);
+ }
}
+ for (int32 i = 0; i < Queue.Count(); i++)
+ {
+ Queue[i]->Execute();
+ }
+ Queue.Clear();
+ Locker.Unlock();
+}
+
+String MainThreadTask::ToString() const
+{
+ return String::Format(TEXT("Main Thread Task ({0})"), ::ToString(GetState()));
}
void MainThreadTask::Enqueue()
{
- MainThreadTasks.Add(this);
+ Locker.Lock();
+ if (InitialDelay <= ZeroTolerance)
+ Queue.Add(this);
+ else
+ Waiting.Add(this);
+ Locker.Unlock();
+}
+
+bool MainThreadActionTask::Run()
+{
+ if (_action1.IsBinded())
+ {
+ _action1();
+ return false;
+ }
+ if (_action2.IsBinded())
+ {
+ return _action2();
+ }
+ return true;
+}
+
+bool MainThreadActionTask::HasReference(Object* obj) const
+{
+ return obj == _target;
}
diff --git a/Source/Engine/Threading/MainThreadTask.h b/Source/Engine/Threading/MainThreadTask.h
index 625c20c70..254733d9d 100644
--- a/Source/Engine/Threading/MainThreadTask.h
+++ b/Source/Engine/Threading/MainThreadTask.h
@@ -24,36 +24,20 @@
class FLAXENGINE_API MainThreadTask : public Task
{
friend class Engine;
-
-protected:
-
- ///
- /// Initializes a new instance of the class.
- ///
- MainThreadTask()
- : Task()
- {
- }
-
private:
+ static void RunAll(float dt);
+
+public:
///
- /// Runs all main thread tasks. Called only by the Engine class.
+ /// The initial time delay (in seconds) before task execution. Use 0 to skip this feature.
///
- static void RunAll();
+ float InitialDelay = 0.0f;
public:
// [Task]
- String ToString() const override
- {
- return String::Format(TEXT("Main Thread Task ({0})"), ::ToString(GetState()));
- }
-
- bool HasReference(Object* obj) const override
- {
- return false;
- }
+ String ToString() const override;
protected:
@@ -127,27 +111,10 @@ public:
protected:
// [MainThreadTask]
- bool Run() override
- {
- if (_action1.IsBinded())
- {
- _action1();
- return false;
- }
-
- if (_action2.IsBinded())
- {
- return _action2();
- }
-
- return true;
- }
+ bool Run() override;
public:
// [MainThreadTask]
- bool HasReference(Object* obj) const override
- {
- return obj == _target;
- }
+ bool HasReference(Object* obj) const override;
};
diff --git a/Source/Engine/Threading/Task.cpp b/Source/Engine/Threading/Task.cpp
index 1c4ad3e1e..f01d4b9e5 100644
--- a/Source/Engine/Threading/Task.cpp
+++ b/Source/Engine/Threading/Task.cpp
@@ -108,45 +108,6 @@ Task* Task::ContinueWith(Function action, Object* target)
return ContinueWith(New(action, target));
}
-Task* Task::Delay(int32 milliseconds)
-{
- class DelayTask : public ThreadPoolTask
- {
- private:
-
- int32 _milliseconds;
- DateTime _startTimeUTC;
-
- public:
-
- DelayTask(int32 milliseconds)
- : _milliseconds(milliseconds)
- {
- }
-
- protected:
-
- // [ThreadPoolTask]
- bool Run() override
- {
- // Take into account the different between task enqueue (OnStart event) and the actual task execution
- auto diff = DateTime::NowUTC() - _startTimeUTC;
- auto ms = Math::Max(0, _milliseconds - (int32)diff.GetTotalMilliseconds());
-
- Platform::Sleep(ms);
-
- return false;
- }
-
- void OnStart() override
- {
- _startTimeUTC = DateTime::NowUTC();
- }
- };
-
- return New(milliseconds);
-}
-
Task* Task::StartNew(Task* task)
{
ASSERT(task);
diff --git a/Source/Engine/Threading/Task.h b/Source/Engine/Threading/Task.h
index 119529f9f..7004c130e 100644
--- a/Source/Engine/Threading/Task.h
+++ b/Source/Engine/Threading/Task.h
@@ -45,36 +45,23 @@ protected:
///
/// The cancel flag used to indicate that there is request to cancel task operation.
///
- volatile int64 _cancelFlag;
+ volatile int64 _cancelFlag = 0;
///
/// The current task state.
///
- volatile TaskState _state;
+ volatile TaskState _state = TaskState::Created;
///
/// The task to start after finish.
///
- Task* _continueWith;
-
-protected:
-
- ///
- /// Initializes a new instance of the class.
- ///
- Task()
- : _cancelFlag(0)
- , _state(TaskState::Created)
- , _continueWith(nullptr)
- {
- }
+ Task* _continueWith = nullptr;
public:
///
- /// Gets work state
+ /// Gets the task state.
///
- /// State
FORCE_INLINE TaskState GetState() const
{
return static_cast(Platform::AtomicRead((int64 volatile*)&_state));
@@ -93,7 +80,6 @@ public:
///
/// Gets the task to start after this one.
///
- /// The next task.
FORCE_INLINE Task* GetContinueWithTask() const
{
return _continueWith;
@@ -102,45 +88,40 @@ public:
public:
///
- /// Checks if operation failed
+ /// Checks if operation failed.
///
- /// True if operation failed, otherwise false
FORCE_INLINE bool IsFailed() const
{
return GetState() == TaskState::Failed;
}
///
- /// Checks if operation has been canceled
+ /// Checks if operation has been canceled.
///
- /// True if operation has been canceled, otherwise false
FORCE_INLINE bool IsCanceled() const
{
return GetState() == TaskState::Canceled;
}
///
- /// Checks if operation has been queued
+ /// Checks if operation has been queued.
///
- /// True if operation has been queued, otherwise false
FORCE_INLINE bool IsQueued() const
{
return GetState() == TaskState::Queued;
}
///
- /// Checks if operation is running
+ /// Checks if operation is running.
///
- /// True if operation is running, otherwise false
FORCE_INLINE bool IsRunning() const
{
return GetState() == TaskState::Running;
}
///
- /// Checks if operation has been finished
+ /// Checks if operation has been finished.
///
- /// True if operation has been finished, otherwise false
FORCE_INLINE bool IsFinished() const
{
return GetState() == TaskState::Finished;
@@ -149,7 +130,6 @@ public:
///
/// Checks if operation has been ended (via cancel, fail or finish).
///
- /// True if operation has been ended, otherwise false
bool IsEnded() const
{
auto state = GetState();
@@ -159,7 +139,6 @@ public:
///
/// Returns true if task has been requested to cancel it's operation.
///
- /// True if task has been canceled and should stop it's work without calling child task start.
FORCE_INLINE bool IsCancelRequested()
{
return Platform::AtomicRead(&_cancelFlag) != 0;
@@ -244,25 +223,6 @@ public:
/// Enqueued task.
Task* ContinueWith(Function action, Object* target = nullptr);
-public:
-
- ///
- /// Creates a task that completes after a specified time interval (not started).
- ///
- /// The time span to wait before completing the returned task.
- /// A task that represents the time delay (not started).
- FORCE_INLINE static Task* Delay(const TimeSpan& delay)
- {
- return Delay(static_cast(delay.GetTotalMilliseconds()));
- }
-
- ///
- /// Creates a task that completes after a specified time interval (not started).
- ///
- /// The amount of milliseconds to wait before completing the returned task.
- /// A task that represents the time delay (not started).
- static Task* Delay(int32 milliseconds);
-
public:
///