129 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			129 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#pragma once
 | 
						|
 | 
						|
/// @defgroup FunctionGuard Function Guard
 | 
						|
/// @brief Scope guard that calls a function as it leaves scope.
 | 
						|
/// @{
 | 
						|
/// 
 | 
						|
/// A FunctionGuard is an scope guard object that stores a functor that will be called from its destructor. By
 | 
						|
/// convention, scope guards are move-only objects that are intended for allocation on the stack, to ensure that certain
 | 
						|
/// operations are performed exactly once (when their scope collapses).
 | 
						|
/// 
 | 
						|
/// Because tasks can be canceled while suspended (and thus do not reach the end of the function), any cleanup code at
 | 
						|
/// the end of a task isn't guaranteed to execute. Because FunctionGuard is an RAII object, it gives programmers an
 | 
						|
/// opportunity to schedule guaranteed cleanup code, no matter how a task terminates.
 | 
						|
/// 
 | 
						|
/// Consider the following example of a task that manages a character's "charge attack" in a combat-oriented game:
 | 
						|
/// 
 | 
						|
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
 | 
						|
/// 
 | 
						|
/// class Character : public Actor
 | 
						|
/// {
 | 
						|
/// public:
 | 
						|
/// 	Task<> ChargeAttackState()
 | 
						|
/// 	{
 | 
						|
/// 		bool bIsFullyCharged = false;
 | 
						|
/// 		if(Input->IsAttackButtonPressed())
 | 
						|
/// 		{
 | 
						|
/// 			StartCharging(); // Start playing charge effects
 | 
						|
/// 			auto stopChargingGuard = MakeFnGuard([&]{
 | 
						|
/// 				StopCharging(); // Stop playing charge effects
 | 
						|
/// 			});
 | 
						|
/// 
 | 
						|
/// 			// Wait for N seconds (canceling if button is no longer held)
 | 
						|
/// 			bIsFullyCharged = co_await WaitSeconds(chargeTime).CancelIf([&] {
 | 
						|
/// 				return !Input->IsAttackButtonPressed();
 | 
						|
/// 			});
 | 
						|
/// 		} // <-- This is when StopCharging() will be called
 | 
						|
/// 		FireShot(bIsFullyCharged);
 | 
						|
/// 	}
 | 
						|
/// };
 | 
						|
/// 
 | 
						|
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
						|
/// 
 | 
						|
/// In the above example, we can guarantee that StopCharging will logically be called exactly once for every call to
 | 
						|
/// StartCharging(), even the ChargeAttackState() task is killed or canceled. Furthermore, we know that StopCharging()
 | 
						|
/// will always be called prior to the call to FireShot().
 | 
						|
/// 
 | 
						|
/// In practice, it is often desirable to create more domain-specific scope guards for specific use cases, but 
 | 
						|
/// FunctionGuard provides a simple general-purpose tool for writing robust, water-tight coroutine logic without the
 | 
						|
/// overhead of creating bespoke support classes.
 | 
						|
 | 
						|
//--- User configuration header ---//
 | 
						|
#include "TasksConfig.h"
 | 
						|
 | 
						|
NAMESPACE_SQUID_BEGIN
 | 
						|
 | 
						|
template <typename tFn = std::function<void()>>
 | 
						|
class FunctionGuard
 | 
						|
{
 | 
						|
public:
 | 
						|
	FunctionGuard() = default; /// Default constructor
 | 
						|
	FunctionGuard(nullptr_t) /// Null-pointer constructor
 | 
						|
	{
 | 
						|
	}
 | 
						|
	FunctionGuard(tFn in_fn) /// Functor constructor
 | 
						|
		: m_fn(std::move(in_fn))
 | 
						|
	{
 | 
						|
	}
 | 
						|
	~FunctionGuard() /// Destructor
 | 
						|
	{
 | 
						|
		Execute();
 | 
						|
	}
 | 
						|
	FunctionGuard(FunctionGuard&& in_other) noexcept /// Move constructor
 | 
						|
		: m_fn(std::move(in_other.m_fn))
 | 
						|
	{
 | 
						|
		in_other.Forget();
 | 
						|
	}
 | 
						|
	FunctionGuard& operator=(FunctionGuard<tFn>&& in_other) noexcept /// Move assignment operator
 | 
						|
	{
 | 
						|
		m_fn = std::move(in_other.m_fn);
 | 
						|
		in_other.Forget();
 | 
						|
		return *this;
 | 
						|
	}
 | 
						|
	FunctionGuard& operator=(nullptr_t) noexcept /// Null-pointer assignment operator (calls Forget() to clear the functor)
 | 
						|
	{
 | 
						|
		Forget();
 | 
						|
		return *this;
 | 
						|
	}
 | 
						|
	operator bool() const /// Convenience conversion operator that calls IsBound()
 | 
						|
	{
 | 
						|
		return IsBound();
 | 
						|
	}
 | 
						|
	bool IsBound() noexcept /// Returns whether functor has been bound to this FunctionGuard
 | 
						|
	{
 | 
						|
		return m_fn;
 | 
						|
	}
 | 
						|
	void Execute() /// Executes and clears the functor (if bound)
 | 
						|
	{
 | 
						|
		if(m_fn)
 | 
						|
		{
 | 
						|
			m_fn.value()();
 | 
						|
			Forget();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	void Forget() noexcept /// Clear the functor (without calling it)
 | 
						|
	{
 | 
						|
		m_fn.reset();
 | 
						|
	}
 | 
						|
 | 
						|
private:
 | 
						|
	std::optional<tFn> m_fn; // The function to call when this scope guard is destroyed
 | 
						|
};
 | 
						|
 | 
						|
/// Create a function guard (directly stores the concretely-typed functor in the FunctionGuard)
 | 
						|
template <typename tFn>
 | 
						|
FunctionGuard<tFn> MakeFnGuard(tFn in_fn)
 | 
						|
{
 | 
						|
	return FunctionGuard<tFn>(std::move(in_fn));
 | 
						|
}
 | 
						|
 | 
						|
/// Create a generic function guard (preferable when re-assigning new functor values to the same variable)
 | 
						|
inline FunctionGuard<> MakeGenericFnGuard(std::function<void()> in_fn)
 | 
						|
{
 | 
						|
	return FunctionGuard<>(std::move(in_fn));
 | 
						|
}
 | 
						|
 | 
						|
NAMESPACE_SQUID_END
 | 
						|
 | 
						|
///@} end of FunctionGuard group
 |