📜 ⬆️ ⬇️

Tips and Tricks 1: deferred function calls (Functor Manager)

When creating games, developers are often faced with such a task - which event should be performed, but not at the moment, but after some time. This task is solved in different ways. Most often, game objects have their own internal timer, and the necessary delay can be realized with its help (by adding extra code to the object). But sometimes you need to make a deferred method call for an object that does not have its own timer, say, hide a window, a row, show an icon or effect after a while, do something else, but not right now, but with a delay. Once or several times.

For these and similar purposes, my colleague developed a universal system, which he called Functor Manager (perhaps there is another term for such systems, I don’t know, I’ll be glad if they tell you).

Initially, the code was written in C ++, and was used in our past projects, now we have a project in C #, so the implementation will be in C #. The brief concept and implementation code (C #) under the cut.
')


Functor



Fanctor is a classic who remembers how long and what function should be called.

So, the fanctor itself consists of:
Guid is a unique identifier that is generated when creating a fanto. This ID is needed so that you can remove the fan in case of uselessness.
_deltaTime - internal timer. This is the time after which the function call should occur.
_func is a pointer to a function whose code must be executed after the timer expires. The function returns the value - after what period (in seconds) the fanctor must repeat the method call. In case the function returns a value of 0.0f, then the fan is nailed.
_funcArg - function argument. Here you can pass say the pointer to the object, or any other necessary information.

In the fan code, everything is simple - the constructors fill in the fields, and the only method worth explaining is the Process method. The counter is reduced by the number of ticks; if it reaches zero, the function is called. The return value is assigned to the timer.

Fan Code:
public delegate float Func(object funcArg); public class HOFunctor { float _deltaTime = 0; Func _func = null; object _funcArg = null; Guid _id = Guid.Empty; public HOFunctor(float deltaTime, Func func, object funcArg) { _id = Guid.NewGuid(); _deltaTime = deltaTime; _func = func; _funcArg = funcArg; } public HOFunctor(float deltaTime, Func func) : this(deltaTime, func, null) { } public bool Process(float deltaTime) { if (_func != null) { _deltaTime -= deltaTime; if (_deltaTime <= 0) { _deltaTime = _func(_funcArg); } } return _deltaTime > 0; } public Guid ID { get { return _id; } } } 


Functor manager



Fanctor Manager is a class, singleton, that stores a list of fanktor (in fact, there are 2 lists, more on this below).

So, in the manager there are 2 lists, one of which is active at the moment, and the second is filled with newly added fans, including those added at the time of execution of the active list. There is also a _currentIndex variable, which is determined by which of the lists is currently active.

Most of the methods are also intuitive (AddFunctor (...) - adds a fanctor to the list, RemoveFunctor () - removes from the list). The only thing worth explaining is the ProcessFunctors (float delta) method. The current list is taken, the process method is called for each fanctor so that it counts the time and, if necessary, runs, if the fantor returns a non-zero value (that is, the time after which it must be called again), then it is added to the new list. At the end of the active list is cleared.

Manager Code:
 public class HOFunctorMgr { #region Private fields private static HOFunctorMgr _instance = null; protected Dictionary<Guid, HOFunctor>[] _functors = { new Dictionary<Guid, HOFunctor>(), new Dictionary<Guid, HOFunctor>() }; int _currentIndex = 0; private HOFunctorMgr() { } #endregion public static HOFunctorMgr Instance { get { if (_instance == null) { _instance = new HOFunctorMgr(); } return _instance; } } #region Public methods public Guid AddFunctor(float deltaTime, Func func, object funcArg) { return AddFunctor(new HOFunctor(deltaTime, func, funcArg)); } public Guid AddFunctor(float deltaTime, Func func) { return AddFunctor(new HOFunctor(deltaTime, func, null)); } public Guid AddFunctor(HOFunctor functor) { if (functor != null && !_functors[_currentIndex].ContainsKey(functor.ID)) { _functors[_currentIndex].Add(functor.ID, functor); return functor.ID; } return Guid.Empty; } public void ProcessFunctors(float delta) { int indexToProcess = _currentIndex; _currentIndex ^= 1; foreach (HOFunctor f in _functors[indexToProcess].Values) { if (f.Process(delta)) { AddFunctor(f); } } _functors[indexToProcess].Clear(); } public void RemoveFunctor(Guid id) { if (_functors[0].ContainsKey(id)) { _functors[0].Remove(id); } if (_functors[1].ContainsKey(id)) { _functors[1].Remove(id); } } #endregion } 


Project Integration



Integration into the project is done in 1 line. Need to add a challenge

HOFunctorMgr.Instance.ProcessFunctors (delta);

in the game cycle, of course, after adding the class of the fan manager’s manager to the project. In my case (the NeoAxis engine) this is the protected override void OnTick method (float delta) of the game window.

delta is the elapsed time in seconds since the previous call.

Everything is so simple.

Examples of using



Single call:

 GameEntities.HOFunctorMgr.Instance.AddFunctor(2.0f, arg => { HidePuzzleWindow(); return 0.0f; }, null ); 


After 2 seconds, HidePuzzleWindow () is called and the puzzle window is hidden.

Multiple call:

 ... int k = 5; HOFunctorMgr.Instance.AddFunctor(5, arg => { GameMap.Instance.AddScreenMessage(string.Format("{0}", arg)); if (k-- >= 0) { return 5; } return 0; }, "test"); 


The fanter will be called 5 times, displaying the word test at an interval of 5 seconds each time. The interval, by the way, can be changed.

Calling a static method:
 static float MyMethod(object arg) { if (arg != null) { ... } return 0; } HOFunctorMgr.Instance.AddFunctor(10, MyMethod, someObject); 


After 10 seconds, the static method MyMethod will be called and someObject will be passed to it as an argument.

About use



You can modify and / or use the code in your projects, both non-commercial and commercial.

Full class code



Class code, neimspace sharpened for NeoAxis.

 using System; using System.Collections.Generic; using System.Text; namespace GameEntities { public delegate float Func(object funcArg); public class HOFunctor { float _deltaTime = 0; Func _func = null; object _funcArg = null; Guid _id = Guid.Empty; public HOFunctor(float deltaTime, Func func, object funcArg) { _id = Guid.NewGuid(); _deltaTime = deltaTime; _func = func; _funcArg = funcArg; } public HOFunctor(float deltaTime, Func func) : this(deltaTime, func, null) { } public bool Process(float deltaTime) { if (_func != null) { _deltaTime -= deltaTime; if (_deltaTime <= 0) { _deltaTime = _func(_funcArg); } } return _deltaTime > 0; } public Guid ID { get { return _id; } } } public class HOFunctorMgr { #region Private fields private static HOFunctorMgr _instance = null; protected Dictionary<Guid, HOFunctor>[] _functors = { new Dictionary<Guid, HOFunctor>(), new Dictionary<Guid, HOFunctor>() }; int _currentIndex = 0; private HOFunctorMgr() { } #endregion public static HOFunctorMgr Instance { get { if (_instance == null) { _instance = new HOFunctorMgr(); } return _instance; } } #region Public methods public Guid AddFunctor(float deltaTime, Func func, object funcArg) { return AddFunctor(new HOFunctor(deltaTime, func, funcArg)); } public Guid AddFunctor(float deltaTime, Func func) { return AddFunctor(new HOFunctor(deltaTime, func, null)); } public Guid AddFunctor(HOFunctor functor) { if (functor != null && !_functors[_currentIndex].ContainsKey(functor.ID)) { _functors[_currentIndex].Add(functor.ID, functor); return functor.ID; } return Guid.Empty; } public void ProcessFunctors(float delta) { int indexToProcess = _currentIndex; _currentIndex ^= 1; foreach (HOFunctor f in _functors[indexToProcess].Values) { if (f.Process(delta)) { AddFunctor(f); } } _functors[indexToProcess].Clear(); } public void RemoveFunctor(Guid id) { if (_functors[0].ContainsKey(id)) { _functors[0].Remove(id); } if (_functors[1].ContainsKey(id)) { _functors[1].Remove(id); } } #endregion } } 


Source: https://habr.com/ru/post/121244/


All Articles