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: ///