{"id":13589566,"url":"https://github.com/zompi2/UE4EnhancedCodeFlow","last_synced_at":"2025-04-08T09:33:16.939Z","repository":{"id":44247482,"uuid":"322938431","full_name":"zompi2/UE4EnhancedCodeFlow","owner":"zompi2","description":"This code plugin provides functions that drastically improve the quality of life during the implementation of game flow in C++.","archived":false,"fork":false,"pushed_at":"2024-05-28T13:51:34.000Z","size":25007,"stargazers_count":69,"open_issues_count":8,"forks_count":12,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-05-29T05:23:44.185Z","etag":null,"topics":["code-flow","coroutines","cpp","ue4","ue4-plugin","ue5","ue5-plugin"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zompi2.png","metadata":{"files":{"readme":"README.md","changelog":"Changelog.txt","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"zompi2","buy_me_a_coffee":"zompi2"}},"created_at":"2020-12-19T21:09:47.000Z","updated_at":"2024-06-10T15:34:20.744Z","dependencies_parsed_at":"2023-02-15T06:45:53.924Z","dependency_job_id":"1f0cfd71-a4e7-4405-a70c-6e2cf6a9b67b","html_url":"https://github.com/zompi2/UE4EnhancedCodeFlow","commit_stats":null,"previous_names":[],"tags_count":33,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zompi2%2FUE4EnhancedCodeFlow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zompi2%2FUE4EnhancedCodeFlow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zompi2%2FUE4EnhancedCodeFlow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zompi2%2FUE4EnhancedCodeFlow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zompi2","download_url":"https://codeload.github.com/zompi2/UE4EnhancedCodeFlow/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223314220,"owners_count":17125028,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["code-flow","coroutines","cpp","ue4","ue4-plugin","ue5","ue5-plugin"],"created_at":"2024-08-01T16:00:31.704Z","updated_at":"2025-04-08T09:33:16.930Z","avatar_url":"https://github.com/zompi2.png","language":"C++","readme":"# Enhanced Code Flow for Unreal Engine\n\nThis code plugin provides functions that drastically improve the quality of life during the implementation of game flow in C++.  \nIt works very well with gameplay programming, UI programming with a lot of transitions or in any other situation.\n\nThe plugin has been tested on the Engine's versions: 4.27, 5.3, 5.4 and 5.5.\n\n# Table of content\n\n- [Fab](#fab)\n- [Example Project](#example-project)\n- [Used in...](#used-in)\n- [Contact](#contact)\n- [Support and Contribution](#support-and-contribution)\n- [Changelog](#changelog)\n- [LEGACY VERSIONS](#legacy-versions)\n\n---\n- [Installation](#installation)\n- [Usage](#usage)\n- [Extra Settings](#extra-settings)\n- [Instanced Actions](#instanced-actions)\n- [Coroutines (experimental)](#coroutines-experimental)\n- [Pausing and Resuming](#pausing-and-resuming)\n- [Stopping Actions](#stopping-actions)\n- [Resetting Actions](#resetting-actions)\n- [Measuring Performance](#measuring-performance)\n- [Logs](#logs)\n- [Extending Plugin](#extending-plugin)\n- [Special Thanks](#special-thanks)\n\n# Fab\n\nThe plugin is available on the Fab! It is free, of course.  \nIf you don't want to build this plugin by yourself, you can **[download it from here](https://www.fab.com/listings/c7a13871-0671-45d5-971c-2f5b3d53d3c0)**.  \nCurrently plugin is available for Unreal Engine version 5.5, 5.4, 5.3, and 4.27.  \nIf you have troubles with downloading version for 4.27 you can download the precompiled package **[from here](https://github.com/zompi2/UE4EnhancedCodeFlow/raw/refs/heads/ue4.27/Pack/EnhancedCodeFlow-4.27.zip)**.  \nThe plugin's version that's on the Fab is **3.4.0**.\n\n[Back to top](#table-of-content)\n\n# Example Project\n\nThe example project wich uses this plugin can be found in **[this repository](https://github.com/zompi2/UE4EnhancedCodeFlowExample)**. Example project is compatible with the newest version of the plugin only.\n\n\u003e !!!IMPORTANT!!!  \n\u003e Currently Example Project will work with **Unreal Engine 5**! The last version of the example project that can be run on UE4 can be found **[here](https://github.com/zompi2/UE4EnhancedCodeFlowExample/tree/Legacy-3.1.1)**. This is a legacy example project which works with ECF 3.1.1. It is not guaranteed that it will work with the newest version of ECF.\n\n![Main](https://github.com/zompi2/UE4EnhancedCodeFlow/assets/7863125/87bf7f3f-2db6-42d5-9195-208a401d84d9)\n\n[Back to top](#table-of-content)\n\n# Used In\n\nEnhanced Code Flow has been used in the following projects:  \n- [Achilles: Legends Untold](https://store.steampowered.com/app/1314000/Achilles_Legends_Untold)\n- [Wanderer's Sigil](https://store.steampowered.com/app/2436870/Wanderers_Sigil)\n- [Blaze in Space: Beat a-Maze](https://store.steampowered.com/app/2016110/Blaze_in_Space_Beat_aMaze)\n- [The Neon Hook](https://zompi.itch.io/the-neon-hook)\n\nIf you are using ECF in your project, let me know :)\n\n[Back to top](#table-of-content)\n\n# Contact\n\nIf you have any question or suggestion regardles this plugin simply add an **Issue** to the github project. I will try my best to answer it quickly :) You can also write an e-mail to me: **zompi2@gmail.com**, however there is a risk that it will be filtered as spam.\n\n[Back to top](#table-of-content)\n\n# Support and Contribution\n\nThis plugin is free and open source forever. However, if you want to show appretiation to my work any support will warm my heart.  \nIf you have any suggestions on how to fix or improve this plugin feel free to create a Pull Request.  \n\n[Back to top](#table-of-content)\n\n# Changelog\n\nThe Changelog has been put into this file: **[Changelog.txt](Changelog.txt)**\n\n[Back to top](#table-of-content)\n\n## **LEGACY VERSIONS**  \n\nVersion `3.1.1` was the last one that had an example project implemented in UE4. Next versions' example projects require UE5 to run.  \nVersion `3.1.1` can be found on a separate branch here: **[Legacy-3.1.1](https://github.com/zompi2/UE4EnhancedCodeFlow/tree/Legacy-3.1.1)**  \n\nVersion `3.0.0` will probably break code and Blueprint nodes from previous version. Update with caution!  \nVersion `2.1.2` can be found on a separate branch here: **[Legacy-2.1](https://github.com/zompi2/UE4EnhancedCodeFlow/tree/Legacy-2.1)**  \n\nVersion `2.0.0` will probably break Blueprint nodes from previous versions. Update with caution!  \nVersion `1.6.1` can be found on a separate branch here: **[Legacy-1.6](https://github.com/zompi2/UE4EnhancedCodeFlow/tree/Legacy-1.6)**\n\n[Back to top](#table-of-content)\n\n\n\n# Installation\n\n1. Get ECF plugin. You can do this by either:\n    * Getting it from the Epic Games Launcher. It will be installed in the Engine's directory: `Engine/Plugins/Marketplace/EnhancedCodeFlow`.\n    * Cloning it or downloading it from this repository and putting it into your project's (`MyProject/Plugins/EnhancedCodeFlow`) or engine's (`Engine/Plugins/EnhancedCodeFlow`) plugins directory.\n2. Add \"EnhancedCodeFlow\" entry to the `PublicDependencyModuleNames` list in your project's `.Build.cs` file.\n3. Enable the plugin in the Editor's Plugins manager (or by changing manually your project's `.uproject` file).\n4. If you want to use this plugin in your code add `#include \"EnhancedCodeFlow.h` to the file in which you want to use the plugin.\n\nCheck out the **[Example Project](https://github.com/zompi2/UE4EnhancedCodeFlowExample)** to see how the plugin is integrated into it.\n\n[Back to top](#table-of-content)\n\n# Usage\n\n- [Delay](#delay)\n- [Delay Ticks](#delay-ticks)\n- [Add Ticker](#add-ticker)\n- [Wait And Execute](#wait-and-execute)\n- [While True Execute](#while-true-execute)\n- [Run Async Then](#run-async-then)\n- [Add Timeline](#add-timeline)\n  - [Add Timeline Vector](#add-timeline-vector)\n  - [Add Timeline Linear Color](#add-timeline-linear-color)\n- [Add Custom Timeline](#add-custom-timeline)\n  - [Add Custom Timeline Vector](#add-custom-timeline-vector)\n  - [Add Custom Timeline Linear Color](#add-custom-timeline-linear-color)\n- [Time Lock](#time-lock)\n- [Do Once](#do-once)\n- [Do N Times](#do-n-times)\n- [Do No More Than X Time](#do-no-more-than-x-time)\n\nRun the following functions to use enhanced code flow!\n\n\u003e Note that every function must receive a pointer to an owner that runs this function in it's first argument.  \n\u003e The owner must be able to return a World via **GetWorld()** function.\n\n#### Delay\n\nExecute specified action after some time. This can be useful in many various situations. Everytime when I was using a Delay node in blueprints I wish there was an equivalent of it in c++.  \nThe `bStopped` tells if this action has been stopped by a Stop function. This argument is optional.\nIf a time parameter is set to 0 it will execute in the next frame. If a time parameter is set less than 0 the action will not execute and will print an error to the log.\n\n``` cpp\nFFlow::Delay(this, 2.f, [this](bool bStopped)\n{\n  // Code to execute after 2 seconds.\n});\n```\n\nAn ECF-Delay BP node has few advantages over the built in Unreal's Delay node.  \nYou can plan to execute delayed code without delaying the whole Blueprint, you can cancel the delayed code's execution or make the dilation game pause and time dilation independent. \n\n![Delay](https://user-images.githubusercontent.com/7863125/218276143-db9554f2-abb3-40a1-ad83-ad1132812bb7.png)\n\n[Back to actions list](#usage)  \n[Back to top](#table-of-content)\n\n#### Delay Ticks\n\nExecute specified action after some ticks. Can be useful if we want to execute some code in next game tick.  \nThe `bStopped` tells if this action has been stopped by a Stop function. This argument is optional.\nIf a number of ticks parameter is set to 0 it will execute in the next frame. If a number of ticks parameter is set less than 0 the action will not execute and will print an error to the log.\n\n``` cpp\nFFlow::DelayTicks(this, 1, [this](bool bStopped)\n{\n  // Code to execute after 1 tick.\n});\n```\n\n![ecfticks](https://github.com/zompi2/UE4EnhancedCodeFlow/assets/7863125/39d1b743-f373-435c-befc-290c90dd720c)\n\n[Back to actions list](#usage)  \n[Back to top](#table-of-content)\n\n#### Add Ticker\n\nCreates a ticker. It can tick specified amount of time or until it won't be stopped or when owning object won't be destroyed.  \nUseful for actors and components that you don't want to be tickeable, but needs one tick to do something.\n\n**Run ticker for 10 seconds**\n\n``` cpp\nFFlow::AddTicker(this, 10.f, [this](float DeltaTime)\n{\n  // Code to execute every tick\n});\n```\n\n**Run ticker for 10 seconds and run a callback when it finishes**\n\n``` cpp\nFFlow::AddTicker(this, 10.f, [this](float DeltaTime)\n{\n  // Code to execute every tick\n}, [this](bool bStopped)\n{\n  // Code to execute when ticker finishes ticking.\n  // The bStopped tells if this action has been stopped by a Stop function.\n  // The bStopped argument is optional.\n});\n```\n\n**Run ticker for infinite time and stop it when you want to**\n\n``` cpp\nFFlow::AddTicker(this, [this](float DeltaTime, FECFHandle TickerHandle)\n{\n  // Code to execute in every tick.\n\n  // Use this to stop the ticker\n  FFlow::StopAction(this, TickerHandle);\n});\n```\n\n**Run ticker for infinite time and something else stops it**\n\n``` cpp\nFECFHandle TickerHandle = FFlow::AddTicker(this, [this](float DeltaTime)\n{\n  // Code to execute in every tick.\n});\n\n// Use this to stop the ticker\nFFlow::StopAction(this, TickerHandle);\n```\n\n\u003e Note 1: Tickers and every other plugin actions are impacted by global time dilation.  \n\u003e Note 2: You can check if the ticker (or any other action) is running using **FFlow::IsActionRunning(TickerHandle)**  \n\u003e Note 3: You can also run ticker infinitely by setting Ticking Time to -1\n\n![Ticker](https://user-images.githubusercontent.com/7863125/218276146-fe27c97e-911d-4af1-980e-54556efc4f08.png)\n\n[Back to actions list](#usage)  \n[Back to top](#table-of-content)\n\n#### Wait and execute\n\nWaits until specific conditions are met and then executes code.  \nThe conditions are defined in a form of a predicate.  \nYou can specify a timeout, which will stop this action after the given time. Setting the timeout value to less or equal 0 will cause this function to run infinitely untill the predicate returns true or when it is explicitly stopped.  \nThe `bStopped` tells if this action has been stopped by a Stop function. This argument is optional.  \nThe `bTimedOut` tells if this action has been stopped because it timed out. This argument is optional.\nPerfect solution if code needs a reference to an object, which spawn moment is not clearly defined, or if you can execute a specific code only when the game reaches a specific state. \n\n\n``` cpp\nFFlow::WaitAndExecute(this, [this](float DeltaTime)\n{\n  // Write your own predicate. \n  // Return true when you want to execute the code below.\n  // The DeltaTime parameter is optional.\n  return bIsReadyToUse;\n},\n[this](bool bTimedOut, bool bStopped)\n{\n  // Implement code to execute when conditions are met or when this action has ran for 5 seconds (time specified in a timeout parameter)\n}, 5.f);\n```\n\nBP version of this function uses a `Predicate` function which controls when the `On Execution` pin will execute.\n\n![WaitAndExecute](https://github.com/zompi2/UE4EnhancedCodeFlow/assets/7863125/882f7637-5f2a-4e7d-b0ef-093da3693a33)\n\n[Back to actions list](#usage)  \n[Back to top](#table-of-content)\n\n#### While true execute\n\nWhile the specified conditions are true tick the given code.  \nThis one is useful when you want to write a loop that executes one run every tick until it finishes it's job.  \nYou can specify a timeout, which will stop this action after the given time. Setting the timeout value to less or equal 0 will cause this function to run infinitely untill the predicate returns false or when it is explicitly stopped.  \nYou can optionally defined what happens when the loop ends.  \n\n``` cpp\nFFlow::WhileTrueExecute(this, [this]()\n{\n  // Write your own predicate. \n  // Return true when you want this action to continue.\n  return bIsRunning;\n},\n[this](float DeltaTime)\n{\n  // Implement code to tick when predicate returns true.\n},\n[this](bool bTimedOut, bool bStopped)\n{\n  // Optionally implement a code that runs when this action ends, even when the condition\n  // in the predicate returns false or it is timed out or it is explicitly stopped.\n  // Both bTimedOut and bStopped arguments are optional.\n}, 0.f);\n```\n\nBP version of this function uses a `Predicate` function which controls when the `On Execution` pin with `Delta Time` will execute.\n\n![WhileTrueExecute](https://github.com/zompi2/UE4EnhancedCodeFlow/assets/7863125/999b064d-9ea9-4a15-9998-8c15bbd10ff0)\n\n[Back to actions list](#usage)  \n[Back to top](#table-of-content)\n\n#### Run Async Then\n\nRuns the given task function on a separate thread and calls the callback function when this task ends.\nYou can specify a timeout, which will stop this action after the given time.  \n\n\u003e Have in mind, that the neither the timeout nor stopping the action will not stop the running async thread. It just won't trigger the callback when the async task ends. Handle timeout on the side of the async task itself.  \n\nThe `bStopped` tells if this action has been stopped by a Stop function. This argument is optional.  \nYou can define the priority of the running task as `Normal` (`AnyBackgroundThreadNormalTask`) or `HiPriority` (`AnyBackgroundHiPriTask`).\n\n\u003e Have in mind, that you can start this function from GameThread only!\n\n\n``` cpp\nFFlow::RunAsyncThen(this, [this]()\n{\n  // This code runs on the background thread.\n},\n[this](bool bTimedOut, bool bStopped)\n{\n  // This code runs on a game thread after the previous block of code finishes it's run.\n}, 0.f, EECFAsyncPrio::Normal);\n```\n\nThe BP node exists for this function, but have in mind that Unreal does not allow for many non-gamethread operations in Blueprints! Use this node with caution!\n\n![runathen](https://github.com/zompi2/UE4EnhancedCodeFlow/assets/7863125/ff9c423e-7a8f-4c33-af6e-d860f3940d82)\n\n[Back to actions list](#usage)  \n[Back to top](#table-of-content)\n\n#### Add timeline\n\nEasily launch the timeline and update your game based on them. Great solution for any kind of blends and transitions.\nThe function requires the following parameters:  \n* StartValue - a value with which the timeline will begin;\n* StopValue - a value with which the timeline will end. StopValue can be lesser than StartValue;\n* Time - how long the timeline will work;\n* TickFunc - a function that will tick with the timeline. It has the following arguments:\n  * Value - a current value on this timeline;\n  * Time - a time that passed on this timeline;\n* CallbackFunc - a function that will run when the timeline comes to an end. Has the same arguments as TickFunc. This function is *optional*;\n* BlendFunc - a function that describes a shape of the timeline:\n  * Linear *(default)*\n  * Cubic\n  * EaseIn\n  * EaseOut\n  * EaseInOut\n* BlendExp - an exponent defining a shape of EaseIn, EaseOut and EaseInOut function shapes. *(default value: 1.f)*;\n\nThe `bStopped` tells if this action has been stopped by a Stop function. This argument is optional.  \n\n``` cpp\nFFlow::AddTimeline(this, 0.f, 1.f, 2.f, [this](float Value, float Time)\n{\n  // Code to run every time the timeline tick\n}, \n[this](float Value, float Time, bool bStopped)\n{\n  // Code to run when timeline stops\n}, \nEECFBlendFunc::ECFBlend_Linear, 2.f);\n```\n\n![Timeline](https://user-images.githubusercontent.com/7863125/218276147-80928cc9-5455-4edd-bd7c-2f50ae819ca3.png)\n\n#### Add timeline vector\n\nThe same as `Add timeline`, but with a Vector instead of float\n\n``` cpp\nFFlow::AddTimelineVector(this, FVector(0.f, 0.f, 0.f), FVector(1.f, 1.f, 1.f), 2.f, [this](FVector Value, float Time)\n{\n  // Code to run every time the timeline tick\n}, \n[this](FVector Value, float Time, bool bStopped)\n{\n  // Code to run when timeline stops\n}, \nEECFBlendFunc::ECFBlend_Linear, 2.f);\n```\n\n![tlvec](https://github.com/user-attachments/assets/a98d8352-1fab-43fb-a15d-2cd9b1fbe0bd)\n\n[Back to actions list](#usage)  \n[Back to top](#table-of-content)\n\n#### Add timeline linear color\n\nThe same as `Add timeline`, but with a LinearColor instead of float \n\n``` cpp\nFFlow::AddTimelineLinearColor(this, FLinearColor(0.f, 0.f, 0.f, 1.f), FLinearColor(1.f, 1.f, 1.f, 1.f), 2.f, [this](FLinearColor Value, float Time)\n{\n  // Code to run every time the timeline tick\n}, \n[this](FLinearColor Value, float Time, bool bStopped)\n{\n  // Code to run when timeline stops\n}, \nEECFBlendFunc::ECFBlend_Linear, 2.f);\n```\n\n![tllc](https://github.com/user-attachments/assets/9555d519-8894-4c7e-a207-e58278abf97e)\n\n#### Add custom timeline\n\nCreates a discrete timeline which shape is based on a **UCurveFloat**. Works like the previously described timeline, but an asset with a curve must be given.\n\n``` cpp\nFFlow::AddCustomTimeline(this, Curve, [this](float Value, float Time)\n{\n  // Code to run every time the timeline tick\n}, \n[this](float Value, float Time, bool bStopped)\n{\n  // Code to run when timeline stops. bStopped argument is optional.\n});\n```\n\n![CustomTimeline](https://user-images.githubusercontent.com/7863125/218276141-1168dd7d-24ab-43bd-901a-bedb3fb9664b.png)\n\n[Back to actions list](#usage)  \n[Back to top](#table-of-content)\n\n#### Add custom timeline vector\n\nThe same as `Add custom timeline` but with **UCurveVector** instead of **UCurveFloat**.\n\n``` cpp\nFFlow::AddCustomTimelineVector(this, Curve, [this](FVector Value, float Time)\n{\n  // Code to run every time the timeline tick\n}, \n[this](FVector Value, float Time, bool bStopped)\n{\n  // Code to run when timeline stops. bStopped argument is optional.\n});\n```\n\n![ctlc](https://github.com/user-attachments/assets/08109d0f-112e-4664-ae4f-c5a7c59dedd4)\n\n[Back to actions list](#usage)  \n[Back to top](#table-of-content)\n\n#### Add custom timeline linear color\n\nThe same as `Add custom timeline` but with **UCurveLinearColor** instead of **UCurveFloat**.\n\n``` cpp\nFFlow::AddCustomTimelineLinearColor(this, Curve, [this](FLinearColor Value, float Time)\n{\n  // Code to run every time the timeline tick\n}, \n[this](FLinearColor Value, float Time, bool bStopped)\n{\n  // Code to run when timeline stops. bStopped argument is optional.\n});\n```\n\n![ctv](https://github.com/user-attachments/assets/e4b88d37-d3ee-4f5a-9b37-4f3ea83fe523)\n\n[Back to actions list](#usage)  \n[Back to top](#table-of-content)\n\n#### Time Lock\n\n**(Instanced)**\n\nBlocks execution of the block of code until the given time has passed.\n\n``` cpp\nstatic FECFInstanceId InstanceId = FECFInstanceId::NewId();\nFFlow::TimeLock(this, 2.f, [this]()\n{\n  // This code will run now, and won't be able to execute for 2 seconds.\n}, InstanceId);\n```\n\nBP version of this function requires `InstanceId` too. The BP node will validate the `InstandeId` from the handler so it just need to be passed into it.\n\n![tlock1](https://user-images.githubusercontent.com/7863125/201354732-26bd20b3-f6b1-433e-8eef-19d0e6e4189d.png)\n\n[Back to actions list](#usage)  \n[Back to top](#table-of-content)\n\n#### Do Once\n\n**(Instanced)**\n\nAllow to execute the given block of code only once.\n\n``` cpp\nstatic FECFInstanceId InstanceId = FECFInstanceId::NewId();\nFFlow::DoOnce(this, [this]()\n{\n  // This code can be run only once.\n}, InstanceId);\n```\n\u003e This function doesn't have a BP version, because Unreal has one already.\n\n[Back to actions list](#usage)  \n[Back to top](#table-of-content)\n\n#### Do N Times\n\n**(Instanced)**\n\nAllow to execute the given block of code only given amount of times.\n\n``` cpp\nstatic FECFInstanceId InstanceId = FECFInstanceId::NewId();\nFFlow::DoNTimes(this, 5, [this](int32 Counter)\n{\n  // This code can be run only 5 times.\n}, InstanceId);\n```\n\n\u003e This function doesn't have a BP version, because Unreal has one already.\n\n[Back to actions list](#usage)  \n[Back to top](#table-of-content)\n\n#### Do No More Than X Time\n\n**(Instanced)**\n\nIt will execute the given block of code immediately, but the next execution will be enqueued and will be called after specified time. There is a parameter which allow to define how many next executions can be enqueued (must be at least 1). If this code will be used when the queue is full - the code will be discarded (not enqueued).\n\n``` cpp\nstatic FECFInstanceId InstanceId = FECFInstanceId::NewId();\nFFlow::DoNoMoreThanXTime(this, [this]()\n{\n  // This code will run now and if called again it will run no earlier than after 5 seconds after the last execution.\n  // If this will be called again before the second execution - it will be discarded.\n}, 5.f, 1, InstanceId);\n```\n\nBP version of this function requires `InstanceId` too. The BP node will validate the `InstandeId` from the handler so it just need to be passed into it.\n\n![donomor](https://user-images.githubusercontent.com/7863125/201354730-e444acc0-c327-48c1-b0f6-3d6cc5855362.png)\n\n[Back to actions list](#usage)  \n[Back to top](#table-of-content)\n\n# Extra settings\n\nYou can define extra settings at the end of each action launch. Currently the following actions are available:\n* Time Intervals - defines the length of one tick.\n* First Delay - defines when the first tick should be performed.\n* Ignore Game Pause - it will ignore the game pause.\n* Ignore Global Time Dilation - it will ignore global time dilation when ticking.\n* Start Paused - the action will start in paused state and must be resumed manually.\n\n``` cpp\nFFlow::AddTicker(this, 10.f, [this](float DeltaTime)\n{\n  // Code to execute every 1 second for 10 seconds.\n}, nullptr, FECFActionSettings(1.f, 0.f, false, false, false));\n```\n\n``` cpp\nFFlow::AddTicker(this, 10.f, [this](float DeltaTime)\n{\n  // Code to execute every tick for 10 seconds \n  // while ignoring global time dilation.\n}, nullptr, FECFActionSettings(0.f, 0.f, true, false, false));\n```\n\n``` cpp\nFFlow::AddTicker(this, 10.f, [this](float DeltaTime)\n{\n  // Code to execute every 1 seconds for 10 seconds, \n  // after 5 seconds have passed, while ignoring \n  // global time dilation and pause.\n}, nullptr, FECFActionSettings(1.f, 5.f, true, true, false));\n```\n\nTo make defining these settings easier there are few macros that creates a settings structure with just one option:\n\n* `ECF_TICKINTERVAL(5.f)` - settings which sets tick interval to 5 second\n* `ECF_DELAYFIRST(1.f)` - settings which makes this action to run after 1 second delay\n* `ECF_IGNOREPAUSE` - settings which makes this action ignore game pause\n* `ECF_IGNORETIMEDILATION` - settings which makes this action ignore global time dilation\n* `ECF_IGNOREPAUSEDILATION` - settings which makes this action ignore pause and global time dilation\n* `ECF_STARTPAUSED` - settings which makes this action started in paused state\n\n``` cpp\nFFlow::Delay(this, 2.f, [this]()\n{\n  // Run this code after 2 seconds, while ignoring game pause.\n}, ECF_IGNOREPAUSE);\n```\n\n![sett](https://user-images.githubusercontent.com/7863125/180844848-3dc7106a-02af-421a-ab9e-4190ab3a4477.png)\n\n[Back to top](#table-of-content)\n\n# Instanced Actions\n\nSome actions can be Instanced. Instanced action is an action that has valid **`FECFInstanceId`**. Such action can be executed only once. \n\nAs long as the action with given valid `FECFInstanceId` is running, no other action with the same `FECFInstanceId` can be executed.\n\n#### Obtaining InstanceId\n\nTo get next valid InstanceId use the `NewId()` function\n\n``` cpp\nFECFInstanceId::NewId();\n```\n\nThere is additional BP node which will validate an `InstanceId` if it is not valid. \n\n![instid](https://user-images.githubusercontent.com/7863125/180844002-8741634d-7c7e-4407-9736-f73417b366c7.png)\n\n[Back to top](#table-of-content)\n\n# Coroutines (experimental)\n\n\u003e Coroutines are treated as an **experimental** feature. You can use them at your own risk!\n\u003e They are experimental, because c++ coroutines are relatively new features and I'm still learning how to implement them correctly. It is possible there will be stability issues.\n\n[Coroutines](https://en.cppreference.com/w/cpp/language/coroutines) are functions that can suspend their execution and be resumed later. They require C++20 which is supported in Unreal Engine from verion 5.3. To make sure that your project supports C++20 add the following line to your project's `Build.cs`:\n\n``` cs\nCppStandard = CppStandardVersion.Cpp20;\n```\n\nEvery coroutine must return the `FECFCoroutine`. ECF implements some helpful coroutines described below. Every coroutine implemented in ECF works simillar to typical ECF action, but they use the coroutine suspension mechanisms instead of lambdas.  \nThey can be paused, resumed, cancelled, resetted and they can accept `FECFActionSettings`.  \nCoroutines doesn't have BP nodes as they are purely code feature.\n\n- [Wait Seconds](#wait-seconds)\n- [Wait Ticks](#wait-ticks)\n- [Wait Until](#wait-until)\n- [Run Async And Wait](#run-async-and-wait)\n- [Getting FECFHandle from FECFCoroutine](#getting-fecfhandle-from-fecfcoroutine)\n- [Checking for coroutine support](#checking-for-coroutine-support)\n\n[Back to top](#table-of-content)\n\n#### Wait Seconds\n\nSuspends the coroutine for a specified amount of seconds. It works like Delay in Blueprints.\n\n``` cpp\nFECFCoroutine UMyClass::SuspandableFunction()\n{\n  // Do something\n  co_await FFlow::WaitSeconds(this, 2.f);\n  // Do something after 2 seconds\n}\n```\n\n[Back to coroutines](#coroutines-experimental)  \n[Back to top](#table-of-content)\n\n#### Wait Ticks\n\nSuspends the coroutine for a specified amount of tick.\n\n``` cpp\nFECFCoroutine UMyClass::SuspandableFunction()\n{\n  // Do something\n  co_await FFlow::WaitTicks(this, 1);\n  // Do something after 1 tick\n}\n```\n\n[Back to coroutines](#coroutines-experimental)  \n[Back to top](#table-of-content)\n\n#### Wait Until\n\nSuspends the coroutine until the given predicate conditions are met.\n\n``` cpp\nFECFCoroutine UMyClass::SuspandableFunction()\n{\n  // Do something\n  co_await FFlow::WaitUntil(this, [this](float DeltaTime)\n  {\n    // Write your own predicate. \n    // Return true when you want to resume the coroutine function.\n    return bIsReadyToUse;\n  }, TimeOut);\n  // Do something after conditions specified in the predicate are met. \n}\n```\n\n[Back to coroutines](#coroutines-experimental)  \n[Back to top](#table-of-content)\n\n#### Run Async And Wait\n\nRuns the given block of code on a background thread and wait for it's completion before moving on.\n\u003e Have in mind, that you can start this coroutine from GameThread only!\n\n``` cpp\nFECFCoroutine UMyClass::SuspandableFunction()\n{\n  // Do something\n  co_await FFlow::RunAsyncAndWait(this, [this]()\n  {\n    // This code will run on a separate background thread.\n  }, TimeOut, EECFAsyncPrio::Normal);\n  // Do something after the above code has finished.\n}\n```\n\n## Getting FECFHandle from FECFCoroutine\n\nIn order to run any cancel, reset or pause actions on coroutine actions you need to have it's `FECFHandle`. You can obtain it from the coroutine handle:\n\n``` cpp\nFECFHandle ActionHandle = SuspandableFunction().promise().ActionHandle;\n```\n\n[Back to coroutines](#coroutines-experimental)  \n[Back to top](#table-of-content)\n\n## Checking for coroutine support\n\nIf you compile your code on multiple compilers and some of them do not support coroutines put the coroutine code into the block:\n``` cpp\n#ifdef __cpp_impl_coroutine\n// coroutine code\n#endif\n```\nThis applies especially to places where you use coroutine keywords, like `co_awai` or `.promise()`.\n\n[Back to coroutines](#coroutines-experimental)  \n[Back to top](#table-of-content)\n\n# Pausing and Resuming\n## Actions\n\nEvery running action can be paused and resumed.\n\n``` cpp\nFFlow::PauseAction(GetWorld(), Handle); // Pause Action\nFFlow::ResumeAction(GetWorld(), Handle); // Resume Action\n\n// Checks if the Action is Running and if it is Paused or not\nbool bIsPaused;\nbool bIsRunning = FFlow::IsActionPaused(WorldContextObject, Handle, bIsPaused);\n```\n\n![pauseas](https://user-images.githubusercontent.com/7863125/180850860-c6548e29-9678-4e22-b70d-f3c22cd74dc7.png)\n\n## Subsystem\nWhole Enhanced Code Subsystem can be paused and resumed as well.\n\n``` cpp\nFFlow::SetPause(GetWorld(), true); // Pauses the whole ECF Subsystem\nFFlow::SetPause(GetWorld(), false); // Unpauses the whole ECF Subsystem\nFFlow::GetPause(GetWorld()); // Check if the whole ECF Subsystem is paused or not\n```\n\n![pausesub](https://user-images.githubusercontent.com/7863125/180851156-863f90f2-07f1-4082-9c46-dd22944d4686.png)\n\n[Back to top](#table-of-content)\n\n# Stopping actions\n\nEvery function described earlier can be checked if it's running and it can be stopped.\n\n``` cpp\nFFlow::IsActionRunning(GetWorld(), Handle); // \u003c- is this action running?\nFFlow::StopAction(GetWorld(), Handle); // \u003c- stops this action and do not call it's callback.\nFFlow::StopAction(GetWorld(), Handle, true); // \u003c- stops this action and call it's callback.\n```\n\nYou can also stop all of the actions from a specific owner or from everywhere.  \nStopped actions can launch their **completion** callbacks or not, depending on the given argument:\n\n``` cpp\nFFlow::StopAllActions(GetWorld()); // \u003c- stops all of the actions (do not call their callbacks)\nFFlow::StopAllActions(GetWorld(), true); // \u003c- stops all of the actions and call their callbacks\nFFlow::StopAllActions(GetWorld(), false, Owner); // \u003c- stops all of the actions started from this specific owner\n```\n\nWhen the **completion** callback will run after the Stop Function, the `bStopped` argument in the completion function of the action will be set to `true`.\n\n![stopping](https://user-images.githubusercontent.com/7863125/180849533-03cb9d37-977f-4c9e-8961-aebd60f8ee25.png)\n\nYou can also stop a specific Instanced action with the **`FECFInstanceId`**:\n\n``` cpp\nFFlow::StopInstancedAction(GetWorld(), InstanceId); // \u003c- stops all actions with this InstanceId\nFFlow::StopInstancedAction(GetWorld(), InstanceId, true); // \u003c- stops all actions with this InstanceId and launch their callbacks\n```\n\n![stopinst](https://user-images.githubusercontent.com/7863125/180849970-246f8d85-33c0-406c-af23-da4cd9244019.png)\n\nYou can also stop all of the **specific** actions. In this case you can also optionally specifiy an owner of this actions, or simply stop all of them.\nYou can also specify if stopped actions should launch their **completion** callbacks or not.\n\n``` cpp\nFFlow::RemoveAllDelays(GetWorld());\nFFlow::RemoveAllTickers(GetWorld());\nFFlow::RemoveAllWaitAndExecutes(GetWorld());\nFFlow::RemoveAllWhileTrueExecutes(GetWorld());\nFFlow::RemoveAllRunAsyncThen(GetWorld());\nFFlow::RemoveAllTimelines(GetWorld());\nFFlow::RemoveAllTimelinesVector(GetWorld());\nFFlow::RemoveAllTimelinesLinearColor(GetWorld());\nFFlow::RemoveAllCustomTimelines(GetWorld());\nFFlow::RemoveAllCustomTimelinesVector(GetWorld());\nFFlow::RemoveAllCustomTimelinesLinearColor(GetWorld());\nFFlow::RemoveAllTimeLocks(GetWorld());\nFFlow::RemoveAllDoNoMoreThanXTimes(GetWorld());\n```\n\n![stopall](https://github.com/user-attachments/assets/953e5379-b403-4400-91ad-31060f84a0a5)\n\nYou can also stop all of the running actions that handle coroutines.\n\n``` cpp\nFFlow::RemoveAllWaitSeconds(GetWorld(), true);\nFFlow::RemoveAllWaitTicks(GetWorld(), true);\nFFlow::RemoveAllWaitUntil(GetWorld(), true);\nFFlow::RemoveAllRunAsyncAndWait(GetWorld(), true);\n```\n\n**IMPORTANT!** If you stop the action which handles a coroutine be aware that if you won't set `bComplete` to true, the suspended coroutine will never be resumed!\n\n[Back to top](#table-of-content)\n\n# Resetting actions\n\nEvery running action, if we know it's handle, can be resetted.\n\n``` cpp\nFFlow::ResetAction(GetWorld(), ActionHandle, false);\n```\n\nThe third parameter tells if after reset the action's callback should run immediately.  \nFor example, if we reset Timeline Action, running callback immediately will run the callback with initial Timeline values right after the reset.  \nOtherwise, ECF will wait for the first next update of the Timeline to run callback.\n\nThere is also a node to run this in Blueprints.\n\n![resa](https://github.com/user-attachments/assets/6acc9703-c68f-4c2b-9976-522dda2150b3)\n\n[Back to top](#table-of-content)\n\n# Measuring Performance\n\n## Stats\n\nTo measure plugin's performance you can use the stat command designed for it: `stat ecf`  \n![stat](https://github.com/zompi2/UE4EnhancedCodeFlow/assets/7863125/ccd5ab03-d1b1-423a-837d-cdd828605508)\nThere are the following stats:  \n* Tick - the time in `ms` the plugin needs to perform one full update.  \n* Actions - the amount of actions that are currently running.\n* Instances - describes how many of the running actions are the instanced ones.\n* Action Objects - the amount of the real action UObjects residing in the memory.\n* Async BP Objects - the amount of the real UObjects handling async BP calls residenting in the memory.\n\n\u003e Have in mind that `Action Objects` and `Async BP Objects` counts Class Default Objects (CDO) too, so their values will never be 0.\n\n\u003e `Tick` measures not only the time the plugin needs to do it's job, but also the time needed to run the code inside action's callbacks.\n\nYou can also display more detailed plugin's performance info with `stat ecfdetails`. It will display the time needed by every type of action.\n\n![image](https://github.com/zompi2/UE4EnhancedCodeFlow/assets/7863125/98e13279-52ba-424f-9100-1e1405ed04f3)\n\n\u003e Have in mind that custom tick intervals might alter the values of stats, especially the `CallCounts`.\n\n## Unreal Insights\n\nYou can measure performence using [Unreal Insights](https://docs.unrealengine.com/4.26/en-US/TestingAndOptimization/PerformanceAndProfiling/UnrealInsights/) tool.  \nIt measures the overall time the plugin needs to perform one full update and the time every single action needs to perform their ticks.\n\nYou can disable Unreal Insights traces for this plugin by setting `bShowLogs` and `bShowVerboseLogs` in `EnhancedCodeFlow.Build.cs` to `false`.\n\n![insight](https://github.com/user-attachments/assets/9fdf838a-722a-4e64-bfc9-bf7bc81cb1ea)\n\n[Back to top](#table-of-content)\n\n# Logs  \n\nECF will print any error and warning that occurred to the output log. You can enable more verbose logging, which will show in more details what ECF is doing.  \nTo enable verbose logging add the following block to `DefaultEngine.ini`:\n\n```ini\n[Core.Log]\nLogECF=Verbose\n```\n\nYou can disable Logs entirely by setting `bEnableInsightProfiling` in `EnhancedCodeFlow.Build.cs` to `false`.\n\n[Back to top](#table-of-content)\n\n# Extending plugin\n\nYou have a source code of this plugin you can easily extend it's functionalities!\n\n\u003e Check how other actions are made to easier understand how to extend the plugin.\n\n1. Create a class that inherits from `UECFActionBase`\n2. Implement `Setup` function, which accepts all parameters you want to pass to this action. \n   `Setup` function must return true if the given parameters are valid.  \n```cpp\nbool Setup(int32 Param1, int32 Param2, TUniqueFunction\u003cvoid(bool)\u003e\u0026\u0026 Callback)\n{\n  CallbackFunc = MoveTemp(Callback);\n  if (CallbackFunc) return true;\n  return false;\n}\n```\n\u003e Any callback must be passed as an r-value reference and be moved to the action's variable.\n\n3. Override `Init` and `Tick` functions if needed.\n4. If you want this action to be stopped while ticking - use `MarkAsFinished()` function.\n5. If you want to launch a callback when this action is stopped by `StopAction` method with `bComplete` set to true - override `Complete(bool bStopped)` function.\n6. If this is an instanced action you can optionally override `RetriggeredInstancedAction()` function to add any logic that should be executed when the instanced action is called while already existing.\n7. You can optionally run `SetMaxActionTime` in action's `Init` function to determine the maximum time in seconds this action should run. \n\u003eIMMPORTANT! SetMaxActionTime is only to help ticker run ticks with proper delta times.  \n\u003eIt will not stop the action itself!\n8. In the `FEnhancedCodeFlow` class implement static function that launches the action using `AddAction` function.\n   The function must receive a pointer to the launching `UObject`, `FECFActionSettings`, `FECFInstanceId` (use invalid one if the action shouldn't be instanced) and every other argument that is used in the action's `Setup` function in the same order.\n   It must return `FECFHandle`.\n```cpp\nFECFHandle FEnhancedCodeFlow::NewAction(const UObject* InOwner, int32 Param1, int32 Param2, TUniqueFunction\u003cvoid(bool)\u003e\u0026\u0026 Callback, const FECFActionSettings\u0026 Settings = {})\n{\n  if (UECFSubsystem* ECF = UECFSubsystem::Get(InOwner))\n    return ECF-\u003eAddAction\u003cUECFNewAction\u003e(InOwner, Settings, FECFInstanceId(), Param1, Param2, MoveTemp(Callback));\n  else\n    return FECFHandle();\n}\n```\n9. You can optionally add the stats counter to your action's `Tick` function, in order to measure it's performence  with `stat ecfdetails`.\n```cpp\nDECLARE_SCOPE_CYCLE_COUNTER(TEXT(\"NewAction - Tick\"), STAT_ECFDETAILS_NEWACTION, STATGROUP_ECFDETAILS);\n```\nIt is done! Now you can run your own action:\n\n```cpp\nFFlow::NewAction(this, 1, 2, [this](bool bStopped)\n{\n  // Callback code.\n}, ECF_IGNOREPAUSE);\n```\n\n### Adding coroutines\n\nAdding actions that supports coroutine is simillar to adding new actions but there are few steps that must be made:\n\n1. Action class must inherits from `UECFCoroutineActionBase` instead of `UECFActionBase`.\n2. Add coroutine task to `ECFCoroutineTasks.h`\n```cpp\nclass ENHANCEDCODEFLOW_API FECFCoroutineTask_NewCoroAction : public FECFCoroutineTask\n{\npublic:\n\n\tFECFCoroutineTask_NewCoroAction(const UObject* InOwner, const FECFActionSettings\u0026 InSettings, int32 InParam1);\n\tvoid await_suspend(FECFCoroutineHandle CoroHandle);\n\nprivate:\n\n\tint32 Param1 = 0;\n};\n```\n3. Implement coroutine task in `ECFCoroutineTasks.cpp`. Use `AddCoroutineAction`` function there.\n```cpp\nFECFCoroutineTask_NewCoroAction::FECFCoroutineTask_NewCoroAction(const UObject* InOwner, const FECFActionSettings\u0026 InSettings, int32 InParam1)\n{\n\tOwner = InOwner;\n\tSettings = InSettings;\n\tParam1 = InParam1;\n}\n\nvoid FECFCoroutineTask_NewCoroAction::await_suspend(FECFCoroutineHandle CoroHandle)\n{\n\tAddCoroutineAction\u003cUNewCoroAction\u003e(Owner, CoroHandle, Settings, Param1);\n}\n```\n4. Coroutine action implementation must resume coroutine in `Complete` function:\n```cpp\nvoid Complete(bool bStopped) override\n{\n\tCoroutineHandle.resume();\n}\n```\n5. The coroutine should be called from `FEnhancedCodeFlow` class and it's implementation should return the defined coroutine task:\n```cpp\nFECFCoroutineTask_NewCoroAction FEnhancedCodeFlow::NewCoroAction(const UObject* InOwner, int32 InParam1, const FECFActionSettings\u0026 Settings)\n{\n\treturn FECFCoroutineTask_NewCoroAction(InOwner, Settings, InParam1);\n}\n```\n\n[Back to top](#table-of-content)\n\n## Disabling build optimization\nYou can temporarily disable plugin's build optimizations by setting the `bDisableOptimization` parameter in `EnhancedCodeFlow.Build.cs` file to `true`. It can help with debugging.\n\n[Back to top](#table-of-content)\n\n# Special thanks\n\nI want to send special thanks to Monika, because she always supports me and believes in me, to Pawel, for allowing me to test this plugin on his project and to everyone that contributed to this project.  \nAlso, I want to thank You for using this plugin! It is very important for me that my work is useful for someone!  \nHappy coding!\n\n[Back to top](#table-of-content)\n","funding_links":["https://github.com/sponsors/zompi2","https://buymeacoffee.com/zompi2"],"categories":["Utilities"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzompi2%2FUE4EnhancedCodeFlow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzompi2%2FUE4EnhancedCodeFlow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzompi2%2FUE4EnhancedCodeFlow/lists"}