Files CCore/inc/DeferCall.h CCore/src/DeferCall.cpp
DeferCall is a defer call engine. It allows record actions and execute them in a defer call loop. This technology is similar to the message loop processing, known in the GUI application development, but is not restricted to a predefined set of messages.
DeferCall is essentially single-threaded. You may run several defer call loops on one per thread basis.
To run a DeferCall loop, you have to derive a class from the DeferCallQueue.
class Queue : public DeferCallQueue { .... public: Queue() { activate(); } ~Queue() { deactivate(); } void forward(TimeScope time_scope) { .... } };
forward() is called when the defer call queue is empty. This method must generate new defer calls or sleep within the given time_scope period. forward() should be used to generate defer calls to handle external events, like key pressing of mouse movement.
Next, you have to create a queue object, then prepare other entities and run the loop.
Queue queue; .... Queue::Loop();
To stop the loop use the DeferCallQueue::Stop(). The global methods address the current active DeferCallQueue object for the current thread. Objects, involved in the DeferQueue processing on this thread, should live withing the queue object life-time.
To add a defer call input to some class use the DeferInput class:
class Window { .... DeferTick defer_tick; private: void put(char ch); void tick(); public: class Input : DeferInput<Window> { friend class Window; explicit Input(Window *window) : DeferInput<Window>(window) {} ~Input() {} public: void put(char ch) { try_post(&Window::put,ch); } }; Input input; Window() : input(this) { defer_tick=input.create(&Window::tick); defer_tick.start(); } };
The class DeferCall serves as the base class of other defer call classes. You don't need to use it directly.
class DeferCall : NoCopy
{
....
public:
DeferCall();
~DeferCall();
virtual void call()=0;
virtual void destroy(DeferCallQueue *defer_queue)=0;
void safeCall();
static void * operator new(std::size_t len,DeferCallQueue *defer_queue);
static void * operator new(std::size_t len,JustTryType,DeferCallQueue *defer_queue) noexcept;
static void operator delete(void *mem,DeferCallQueue *defer_queue);
static void operator delete(void *mem,JustTryType,DeferCallQueue *defer_queue);
};
call() is defined in a derived class and is an actual processing method.
destroy() is defined in a derived class to properly destroy the object.
safeCall() calls the call() and catches and ignores all exceptions.
Other methods are used to allocate and free memory for the object from the given DeferCallQueue.
The class DeferCallHeap is used to allocate and free memory for DeferCall objects. An object of this class is used by a DeferCallQueue object for the memory management. You don't need to use this class directly.
class DeferCallHeap : NoCopy
{
....
public:
explicit DeferCallHeap(ulen mem_len);
~DeferCallHeap();
void * alloc(ulen len);
void free(void *mem);
static ulen GetMaxLen(); // max cached length
};
The memory is taken from a memory block of mem_len length. This block is allocated by the constructor and released by the destructor.
alloc() returns null, if there is no memory available.
free() argument must not be null.
GetMaxLen() is used for the memory allocation caching. To speed up memory operations for short objects DeferCallHeap maintains a list of short memory blocks up to some count for such allocations. GetMaxLen() determines the maximum short allocation length.
The class DeferCallQueue is a main DeferCall engine class. It maintains the list of DeferCalls and executes them in a loop. The object of this type is registered per thread as the current thread DeferCall loop object.
class DeferCallQueue : NoCopy
{
....
private:
virtual void forward(TimeScope time_scope)=0;
protected:
void activate();
void deactivate();
public:
// constructors
static const ulen DefaultMemLen = 1_MByte ;
explicit DeferCallQueue(ulen mem_len=DefaultMemLen);
~DeferCallQueue();
// heap
void * try_alloc(ulen len);
void * alloc(ulen len);
void free(void *mem); // mem!=0
template <class T>
void destroy(T *obj);
// call
void post(DeferCall *defer_call);
void post_first(DeferCall *defer_call);
void start_tick(DeferCall *defer_call);
void stop_tick(DeferCall *defer_call);
// loop
static constexpr MSec DefaultTickPeriod = 40_msec ; // 25 Hz
void loop(MSec tick_period=DefaultTickPeriod);
void stop();
// per-thread queue
static DeferCallQueue * Get();
static void Loop(MSec tick_period=DefaultTickPeriod);
static void Stop();
};
forward() must be defined in a derived class. This method is called when the defer call queue is empty.
activate() must be called in a derived class constructor to register the object with the current thread. If some object is already registered, an exception is thrown.
deactivate() must be called in a derived class destructor to unregister the object with the current thread.
DefaultMemLen is the default heap capacity.
mem_len is a heap capacity. All DeferCall objects for this loop are created in the heap of this capacity.
The following four methods are heap methods. You don't need to use these methods directly.
try_alloc() returns null, if there is no memory.
alloc() throws an exception, if there is no memory. It also stops the defer call loop in this case.
free() releases the memory, the argument must not be null.
destroy() destroys a DeferCall object.
The following four methods are queue methods. You don't need to use these methods directly. Arguments must not be null.
post() puts the DeferCall object in the queue. The object is consumed. It will be destroyed after processing or during the queue cleanup after stop.
post_first() puts the DeferCall object in the queue head. The object is consumed. It will be destroyed after processing or during the queue cleanup after stop.
start_tick() starts ticks on the object. The object is not consumed. It retains the owner. Defer tick is a periodic event, during this event all specified defer calls are called to do a periodic processing.
stop_tick() stops ticks on the object. The object can be destroyed now.
The next two methods are loop methods. You don't need to use these methods directly.
loop() starts the defer calls loop. The argument is the tick period, defaulted to the DefaultTickPeriod, equals 40 milliseconds, that gives 25 Hz tick frequency. The tick period is maintained as accurate, as possible.
stop() stops the loop.
Finally, three global methods.
Get() gets the active DeferCallQueue object. An exception is thrown, if there is no one.
Loop() starts the defer call loop.
Stop() stops the loop.
The class DeferCouple is a helper class, it stores a pointer to a DeferCallQueue object and a pointer to a DeferCall object. You don't need to use this class directly. If you desire to do it, think throw how its methods are working, they have been designed to serve a particular purpose.
struct DeferCouple
{
DeferCallQueue *defer_queue;
DeferCall *defer_call;
// constructors
DeferCouple();
DeferCouple(NothingType) : DeferCouple() {}
DeferCouple(DeferCallQueue *defer_queue,DeferCall *defer_call);
// props
bool operator ! () const { return !defer_queue; }
// cleanup
void cleanup() noexcept(EnableNoExcept);
// post
void post() { defer_queue->post(defer_call); }
void post_first() { defer_queue->post_first(defer_call); }
void try_post() { if( defer_queue ) defer_queue->post(defer_call); }
void try_post_first() { if( defer_queue ) defer_queue->post_first(defer_call); }
void start_tick() { defer_queue->start_tick(defer_call); }
void stop_tick() { defer_queue->stop_tick(defer_call); }
};
Default constructor and NothingType-constructor creates a null object.
The second constructor creates an object with given object pointers. If the DeferCall pointer is null, a null object is constructed.
There is no destructor, so you have to cleanup the object manually by the method cleanup().
operator !() returns true, if the object is null.
cleanup() destroys the DeferCall object and nullifies the object.
Other methods posts the defer call or starts the defer tick on it.
The class DeferTick is a defer tick controller. It may own some defer call, given as a DeferCouple object. If it owns some defer call, the defer tick can be started on this defer call.
class DeferTick : NoCopy
{
....
public:
// constructors
explicit DeferTick(DeferCouple couple_={});
~DeferTick();
// set/reset
bool set(DeferCouple couple);
bool reset();
void operator = (DeferCouple couple);
// start/stop
bool start();
bool stop();
};
Constructor builds the object, owning the given defer call, provided as a DeferCouple object. If the DeferCouple object is null, the tick object is null.
Destructor resets the object. If a defer call object was owned, it is destroyed.
set() and operator = sets a new defer call object, or null. The previous defer call is destroyed, if any. If the defer tick was started, it remains started on the new defer call, unless null is provided. In the last case the tick becomes stopped and the return value is true.
reset() is the same as set(Nothing). I.e. it destroys the old defer call object and resets the tick object to the null state. The defer tick is stopped, if it was running. In the last case the return value is true.
start() starts the defer tick on the owned defer call, if any. The return value is true, if the defer tick has been successfully started.
stop() stops the defer tick, if it is running. The return value is true, if the defer tick has been successfully stopped.
All four methods return true, if the defer tick status (running or stopped) has been changed.
The class DeferInput is the main "input" class. It is used to build and use defer calls, bounded with method calls of some object.
template <class S>
class DeferInput : NoCopy
{
....
public:
template <class ... TT>
static constexpr ulen GetMessageLength();
// constructors
explicit DeferInput(S *obj);
~DeferInput();
void cancel();
// post
template <class ... TT>
DeferCouple create(void (S::* method)(TT... tt),const TT & ... tt);
template <class ... TT>
void post(void (S::* method)(TT... tt),const TT & ... tt);
template <class ... TT>
void post_first(void (S::* method)(TT... tt),const TT & ... tt);
// try post
template <class ... TT>
DeferCouple try_create(void (S::* method)(TT... tt),const TT & ... tt);
template <class ... TT>
void try_post(void (S::* method)(TT... tt),const TT & ... tt);
template <class ... TT>
void try_post_first(void (S::* method)(TT... tt),const TT & ... tt);
// post interface
template <class FuncInit>
DeferCouple create(FuncInit func_init); // func(S &)
template <class FuncInit>
void post(FuncInit func_init);
template <class FuncInit>
void post_first(FuncInit func_init);
// try post interface
template <class FuncInit>
DeferCouple try_create(FuncInit func_init); // func(S &)
template <class FuncInit>
void try_post(FuncInit func_init);
template <class FuncInit>
void try_post_first(FuncInit func_init);
};
The constructor argument is a pointer to the target object. It should be non-null (otherwise the object is useless). The object life-time must be greater than the DeferInput object life-time. Constructor also captures the pointer to the current DeferCallQueue object. So the input object must be used withing the thread has created the input object and after the thread is attached with a DeferCallQueue object.
cancel() deactivates all pending defer calls. They call() methods become "doing nothing".
There are four groups of methods for the defer call creation and submission. The first method creates a defer call and returns one as a DeferCouple object. The second creates and posts the defer call. The third creates and posts at the queue head.
The first group creates a defer call from an object method and a set of arguments. Rememebr, the arguments are copied and stored, so you must be caution with arguments of a "pointer type". Also if the method is failed due to the lack of memory, the defer call loop will be stopped. The method can also throw an exception from the argument copying. This situation is considered as a bad design, it's better avoid it. Don't use types with not efficient copy constructors!
create creates a defer call from the given object method and the set of arguments. You need this method for the defer tick processing.
post() creates and post a defer call from the given object method and the set of arguments.
post_first() creates and post at the head a defer call from the given object method and the set of arguments.
The second group of methods is try variants.
try_create() is the same as the create(), but does not throw exception if no memory. In this case the method returns a null object.
try_post() is the same as the post(), but does not throw exception if no memory. In this case the method does nothing.
try_post_first() is the same as the post_first(), but does not throw exception if no memory. In this case the method does nothing.
Last methods are similar, but create defer calls not from object methods, but from functor initialization objects. Each time the defer call is called, the functor is created from the initializer and applied to the object.