/* * Copyright 2013-2019 Software Radio Systems Limited * * This file is part of srsLTE. * * srsLTE is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * srsLTE is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * A copy of the GNU Affero General Public License can be found in * the LICENSE file in the top-level directory of this distribution * and at http://www.gnu.org/licenses/. * */ #include #include #include #include #ifndef SRSLTE_RESUMABLE_PROCEDURES_H #define SRSLTE_RESUMABLE_PROCEDURES_H namespace srslte { enum class proc_state_t { on_going, success, error, inactive }; enum class proc_outcome_t { repeat, yield, success, error }; /************************************************************************************** * class: proc_impl_t * Provides an polymorphic interface for resumable procedures. This base can then be used * by a task dispatch queue via the method "run()". * Every procedure starts in inactive state, and finishes with success or error. * With methods: * - run() - executes a procedure, returning true if the procedure is still running * or false, if it has completed * - step() - method overriden by child class that will be called by run(). step() * executes a procedure "action" based on its current internal state, * and return a proc_outcome_t variable with possible values: * - yield - the procedure performed the action but hasn't completed yet. * - repeat - the same as yield, but explicitly asking that run() should * recall step() again (probably the procedure state has changed) * - error - the procedure has finished unsuccessfully * - success - the procedure has completed successfully * - stop() - called automatically when a procedure has finished. Useful for actions * upon procedure completion, like sending back a response. * - set_proc_state() / is_#() - setter and getters for current procedure state ************************************************************************************/ class proc_impl_t { public: proc_impl_t() : proc_state(proc_state_t::inactive) {} virtual ~proc_impl_t() = default; virtual proc_outcome_t step() = 0; virtual void stop() {} // may be overloaded bool run() { proc_outcome_t outcome = proc_outcome_t::repeat; while (is_running() and outcome == proc_outcome_t::repeat) { outcome = step(); if (outcome == proc_outcome_t::error) { set_proc_state(proc_state_t::error); } else if (outcome == proc_outcome_t::success) { set_proc_state(proc_state_t::success); } } return is_running(); } void set_proc_state(proc_state_t new_state) { proc_state = new_state; if (proc_state == proc_state_t::error or proc_state == proc_state_t::success) { stop(); } } bool is_error() const { return proc_state == proc_state_t::error; } bool is_success() const { return proc_state == proc_state_t::success; } bool is_running() const { return proc_state == proc_state_t::on_going; } bool is_complete() const { return is_success() or is_error(); } bool is_active() const { return proc_state != proc_state_t::inactive; } private: proc_state_t proc_state; }; /************************************************************************************** * class: proc_t * Handles the lifetime, of a procedure T that derives from proc_impl_t, including * its alloc, initialization, and reset back to initial state once the procedure has been * completed and the user has extracted its results. * Can only be re-launched when a procedure T becomes inactive. * It uses a unique_ptr to allow the use of procedures that are forward declared. * It provides the following methods: * - run() - calls proc_impl_t::run(). See above. * - launch() - initializes the procedure T by calling T::init(...). Handles the case * of failed initialization, and forbids the initialization of procedures * that are already active. * - pop() - extracts the result of the procedure if it has finished, and sets * proc_t back to inactive * - trigger_event(Event) - used for handling external events. The procedure T will * have to define a method "trigger_event(Event)" as well, * specifying how each event type should be handled. ************************************************************************************/ template class proc_t { public: explicit proc_t() : proc_impl_ptr(new T()) {} T* get() { return proc_impl_ptr.get(); } bool is_active() const { return proc_impl_ptr->is_active(); } bool is_complete() const { return proc_impl_ptr->is_complete(); } T* release() { return proc_impl_ptr.release(); } bool run() { return proc_impl_ptr->run(); } void clear() { // Destructs the current object, and calls default ctor (which sets proc back to inactive and ready for another run) proc_impl_ptr->~T(); new (proc_impl_ptr.get()) T(); } template void trigger_event(Event&& e) { if (proc_impl_ptr->is_running()) { proc_outcome_t outcome = proc_impl_ptr->trigger_event(std::forward(e)); if (outcome == proc_outcome_t::error) { proc_impl_ptr->set_proc_state(proc_state_t::error); } else if (outcome == proc_outcome_t::success) { proc_impl_ptr->set_proc_state(proc_state_t::success); } } } T pop() { if (not proc_impl_ptr->is_complete()) { return T(); } T ret(std::move(*proc_impl_ptr)); clear(); return ret; } template bool launch(Args&&... args) { if (is_active()) { // if already active return false; } proc_impl_ptr->set_proc_state(proc_state_t::on_going); proc_outcome_t init_ret = proc_impl_ptr->init(std::forward(args)...); switch (init_ret) { case proc_outcome_t::error: proc_impl_ptr->set_proc_state(proc_state_t::error); // call stop as an error clear(); return false; case proc_outcome_t::success: proc_impl_ptr->set_proc_state(proc_state_t::success); break; case proc_outcome_t::repeat: run(); // call run right away break; case proc_outcome_t::yield: break; } return true; } private: std::unique_ptr proc_impl_ptr; }; /************************************************************************************** * class: func_proc_t * A proc_impl_t used to store lambda functions and other function pointers as a step() * method, avoiding this way, always having to create a new class per procedure. ************************************************************************************/ class func_proc_t : public proc_impl_t { public: proc_outcome_t init(std::function step_func_) { step_func = std::move(step_func_); return proc_outcome_t::yield; } proc_outcome_t step() final { return step_func(); } private: std::function step_func; }; /************************************************************************************** * class: func_proc_t * A helper proc_impl_t whose step()/stop() are no op, but has a trigger_event() that * signals that the method has finished and store a result of type OutcomeType. ************************************************************************************/ template class query_proc_t : public proc_impl_t { public: proc_outcome_t init() { return proc_outcome_t::yield; } proc_outcome_t step() final { return proc_outcome_t::yield; } proc_outcome_t trigger_event(const OutcomeType& outcome_) { outcome = outcome_; return proc_outcome_t::success; } const OutcomeType& result() const { return outcome; } private: OutcomeType outcome; }; /************************************************************************************** * class: callback_list_t * Stores procedures that derive from proc_impl_t. Its run() method calls sequentially * all the stored procedures run() method, and removes the procedures if they have * completed. * There are different ways to add a procedure to the list: * - add_proc(...) - adds a proc_t, and once the procedure has completed, takes it * out of the container without resetting it back to its initial state * or deleting. This is useful, if the user wants to extract the * procedure result via proc_t::pop() * - consume_proc(...) - receives a proc_t as a rvalue, and calls the proc_t * destructor once the procedure has ended. Useful, for procedures * for which the user is not interested in the result, or reusing * - defer_proc(...) - same as add_proc(...), but once the procedure has finished, it * automatically sets the procedure back to its initial state. * Useful if the user is not interested in handling the result * - defer_task(...) - same as consume_proc(...) but takes a function pointer that * specifies a proc_impl_t step() function ************************************************************************************/ class callback_list_t { public: typedef std::function proc_deleter_t; typedef std::unique_ptr callback_obj_t; template struct recycle_deleter_t { void operator()(proc_impl_t* p) { if (p != nullptr) { T* Tp = static_cast(p); Tp->~T(); new (Tp) T(); } } }; template void add_proc(proc_t& proc) { if (proc.is_complete()) { return; } callback_obj_t ptr(proc.get(), [](proc_impl_t* p) { /* do nothing */ }); callbacks.push_back(std::move(ptr)); } template void consume_proc(proc_t&& proc) { if (proc.is_complete()) { return; } callback_obj_t ptr(proc.release(), std::default_delete()); callbacks.push_back(std::move(ptr)); } template void defer_proc(proc_t& proc) { if (proc.is_complete()) { proc.pop(); return; } callback_obj_t ptr(proc.get(), recycle_deleter_t()); callbacks.push_back(std::move(ptr)); } bool defer_task(std::function step_func) { proc_t proc; if (not proc.launch(std::move(step_func))) { return false; } consume_proc(std::move(proc)); return true; } void run() { // Calls run for all callbacks. Remove the ones that have finished. The proc dtor is called. callbacks.remove_if([](callback_obj_t& elem) { return not elem->run(); }); } size_t size() const { return callbacks.size(); } private: std::list callbacks; }; } // namespace srslte #endif // SRSLTE_RESUMABLE_PROCEDURES_H