Add Delay node to Visual Scripting

This commit is contained in:
Wojtek Figat
2021-06-14 16:10:08 +02:00
parent 90105c421d
commit f5cdb0abfd
9 changed files with 170 additions and 144 deletions

View File

@@ -293,6 +293,21 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(1, "Value", true, null, 1), 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),
}
},
}; };
} }
} }

View File

@@ -677,14 +677,16 @@ namespace FlaxEditor.Surface.Archetypes
/// <inheritdoc /> /// <inheritdoc />
public ThisNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) public ThisNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch) : base(id, context, nodeArch, groupArch)
{} {
}
/// <inheritdoc /> /// <inheritdoc />
public override void OnLoaded() public override void OnLoaded()
{ {
base.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); var box = (OutputBox)GetBox(0);
box.CurrentType = type ? type : new ScriptType(typeof(VisualScript)); box.CurrentType = type ? type : new ScriptType(typeof(VisualScript));
} }

View File

@@ -19,6 +19,7 @@
#include "Engine/Serialization/JsonWriter.h" #include "Engine/Serialization/JsonWriter.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Utilities/StringConverter.h" #include "Engine/Utilities/StringConverter.h"
#include "Engine/Threading/MainThreadTask.h"
#include "FlaxEngine.Gen.h" #include "FlaxEngine.Gen.h"
namespace namespace
@@ -83,6 +84,41 @@ VisualScriptExecutor::VisualScriptExecutor()
_perGroupProcessCall[17] = (ProcessBoxHandler)&VisualScriptExecutor::ProcessGroupFlow; _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<VisualScript>(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<ScriptingObject>(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) VisjectExecutor::Value VisualScriptExecutor::eatBox(Node* caller, Box* box)
{ {
// Check if graph is looped or is too deep // Check if graph is looped or is too deep
@@ -1273,6 +1309,46 @@ void VisualScriptExecutor::ProcessGroupFlow(Box* boxBase, Node* node, Value& val
} }
} }
break; 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<DelayTask>();
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;
} }
} }
} }

View File

@@ -36,6 +36,8 @@ public:
/// </summary> /// </summary>
VisualScriptExecutor(); VisualScriptExecutor();
void Invoke(const Guid& scriptId, int32 nodeId, int32 boxId, const Guid& instanceId, Variant& result) const;
private: private:
Value eatBox(Node* caller, Box* box) override; Value eatBox(Node* caller, Box* box) override;
Graph* GetCurrentGraph() const override; Graph* GetCurrentGraph() const override;

View File

@@ -288,7 +288,7 @@ void Engine::OnUpdate()
// Simulate lags // Simulate lags
//Platform::Sleep(100); //Platform::Sleep(100);
MainThreadTask::RunAll(); MainThreadTask::RunAll(Time::Update.UnscaledDeltaTime.GetTotalSeconds());
// Call event // Call event
Update(); Update();

View File

@@ -1,25 +1,68 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "MainThreadTask.h" #include "MainThreadTask.h"
#include "ConcurrentTaskQueue.h" #include "Engine/Platform/CriticalSection.h"
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
ConcurrentTaskQueue<MainThreadTask> MainThreadTasks; namespace
void MainThreadTask::RunAll()
{ {
// TODO: use bulk dequeue CriticalSection Locker;
Array<MainThreadTask*> Waiting;
Array<MainThreadTask*> Queue;
}
void MainThreadTask::RunAll(float dt)
{
PROFILE_CPU(); PROFILE_CPU();
Locker.Lock();
MainThreadTask* task; for (int32 i = Waiting.Count() - 1; i >= 0; i--)
while (MainThreadTasks.try_dequeue(task))
{ {
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() 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;
} }

View File

@@ -24,36 +24,20 @@
class FLAXENGINE_API MainThreadTask : public Task class FLAXENGINE_API MainThreadTask : public Task
{ {
friend class Engine; friend class Engine;
protected:
/// <summary>
/// Initializes a new instance of the <see cref="MainThreadTask"/> class.
/// </summary>
MainThreadTask()
: Task()
{
}
private: private:
static void RunAll(float dt);
public:
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
static void RunAll(); float InitialDelay = 0.0f;
public: public:
// [Task] // [Task]
String ToString() const override String ToString() const override;
{
return String::Format(TEXT("Main Thread Task ({0})"), ::ToString(GetState()));
}
bool HasReference(Object* obj) const override
{
return false;
}
protected: protected:
@@ -127,27 +111,10 @@ public:
protected: protected:
// [MainThreadTask] // [MainThreadTask]
bool Run() override bool Run() override;
{
if (_action1.IsBinded())
{
_action1();
return false;
}
if (_action2.IsBinded())
{
return _action2();
}
return true;
}
public: public:
// [MainThreadTask] // [MainThreadTask]
bool HasReference(Object* obj) const override bool HasReference(Object* obj) const override;
{
return obj == _target;
}
}; };

View File

@@ -108,45 +108,6 @@ Task* Task::ContinueWith(Function<bool()> action, Object* target)
return ContinueWith(New<ThreadPoolActionTask>(action, target)); return ContinueWith(New<ThreadPoolActionTask>(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<DelayTask>(milliseconds);
}
Task* Task::StartNew(Task* task) Task* Task::StartNew(Task* task)
{ {
ASSERT(task); ASSERT(task);

View File

@@ -45,36 +45,23 @@ protected:
/// <summary> /// <summary>
/// The cancel flag used to indicate that there is request to cancel task operation. /// The cancel flag used to indicate that there is request to cancel task operation.
/// </summary> /// </summary>
volatile int64 _cancelFlag; volatile int64 _cancelFlag = 0;
/// <summary> /// <summary>
/// The current task state. /// The current task state.
/// </summary> /// </summary>
volatile TaskState _state; volatile TaskState _state = TaskState::Created;
/// <summary> /// <summary>
/// The task to start after finish. /// The task to start after finish.
/// </summary> /// </summary>
Task* _continueWith; Task* _continueWith = nullptr;
protected:
/// <summary>
/// Initializes a new instance of the <see cref="Task"/> class.
/// </summary>
Task()
: _cancelFlag(0)
, _state(TaskState::Created)
, _continueWith(nullptr)
{
}
public: public:
/// <summary> /// <summary>
/// Gets work state /// Gets the task state.
/// </summary> /// </summary>
/// <returns>State</returns>
FORCE_INLINE TaskState GetState() const FORCE_INLINE TaskState GetState() const
{ {
return static_cast<TaskState>(Platform::AtomicRead((int64 volatile*)&_state)); return static_cast<TaskState>(Platform::AtomicRead((int64 volatile*)&_state));
@@ -93,7 +80,6 @@ public:
/// <summary> /// <summary>
/// Gets the task to start after this one. /// Gets the task to start after this one.
/// </summary> /// </summary>
/// <returns>The next task.</returns>
FORCE_INLINE Task* GetContinueWithTask() const FORCE_INLINE Task* GetContinueWithTask() const
{ {
return _continueWith; return _continueWith;
@@ -102,45 +88,40 @@ public:
public: public:
/// <summary> /// <summary>
/// Checks if operation failed /// Checks if operation failed.
/// </summary> /// </summary>
/// <returns>True if operation failed, otherwise false</returns>
FORCE_INLINE bool IsFailed() const FORCE_INLINE bool IsFailed() const
{ {
return GetState() == TaskState::Failed; return GetState() == TaskState::Failed;
} }
/// <summary> /// <summary>
/// Checks if operation has been canceled /// Checks if operation has been canceled.
/// </summary> /// </summary>
/// <returns>True if operation has been canceled, otherwise false</returns>
FORCE_INLINE bool IsCanceled() const FORCE_INLINE bool IsCanceled() const
{ {
return GetState() == TaskState::Canceled; return GetState() == TaskState::Canceled;
} }
/// <summary> /// <summary>
/// Checks if operation has been queued /// Checks if operation has been queued.
/// </summary> /// </summary>
/// <returns>True if operation has been queued, otherwise false</returns>
FORCE_INLINE bool IsQueued() const FORCE_INLINE bool IsQueued() const
{ {
return GetState() == TaskState::Queued; return GetState() == TaskState::Queued;
} }
/// <summary> /// <summary>
/// Checks if operation is running /// Checks if operation is running.
/// </summary> /// </summary>
/// <returns>True if operation is running, otherwise false</returns>
FORCE_INLINE bool IsRunning() const FORCE_INLINE bool IsRunning() const
{ {
return GetState() == TaskState::Running; return GetState() == TaskState::Running;
} }
/// <summary> /// <summary>
/// Checks if operation has been finished /// Checks if operation has been finished.
/// </summary> /// </summary>
/// <returns>True if operation has been finished, otherwise false</returns>
FORCE_INLINE bool IsFinished() const FORCE_INLINE bool IsFinished() const
{ {
return GetState() == TaskState::Finished; return GetState() == TaskState::Finished;
@@ -149,7 +130,6 @@ public:
/// <summary> /// <summary>
/// Checks if operation has been ended (via cancel, fail or finish). /// Checks if operation has been ended (via cancel, fail or finish).
/// </summary> /// </summary>
/// <returns>True if operation has been ended, otherwise false</returns>
bool IsEnded() const bool IsEnded() const
{ {
auto state = GetState(); auto state = GetState();
@@ -159,7 +139,6 @@ public:
/// <summary> /// <summary>
/// Returns true if task has been requested to cancel it's operation. /// Returns true if task has been requested to cancel it's operation.
/// </summary> /// </summary>
/// <returns>True if task has been canceled and should stop it's work without calling child task start.</returns>
FORCE_INLINE bool IsCancelRequested() FORCE_INLINE bool IsCancelRequested()
{ {
return Platform::AtomicRead(&_cancelFlag) != 0; return Platform::AtomicRead(&_cancelFlag) != 0;
@@ -244,25 +223,6 @@ public:
/// <returns>Enqueued task.</returns> /// <returns>Enqueued task.</returns>
Task* ContinueWith(Function<bool()> action, Object* target = nullptr); Task* ContinueWith(Function<bool()> action, Object* target = nullptr);
public:
/// <summary>
/// Creates a task that completes after a specified time interval (not started).
/// </summary>
/// <param name="delay">The time span to wait before completing the returned task.</param>
/// <returns>A task that represents the time delay (not started).</returns>
FORCE_INLINE static Task* Delay(const TimeSpan& delay)
{
return Delay(static_cast<int32>(delay.GetTotalMilliseconds()));
}
/// <summary>
/// Creates a task that completes after a specified time interval (not started).
/// </summary>
/// <param name="milliseconds">The amount of milliseconds to wait before completing the returned task.</param>
/// <returns>A task that represents the time delay (not started).</returns>
static Task* Delay(int32 milliseconds);
public: public:
/// <summary> /// <summary>