node-haystack Episode-4: Wrapper of libuv

Preface

Before starting our project: node-haystack, we need some handful utilities. Using the C style libuv is boring. Then here comes the wrappers.

Environment

Hardware

Type Brand Capacity Frequency
MB MSI-Z77HD
Memory N/A 32GB 1600Mhz
CPU Intel® Core™ i7-4790 4Core Hyper-thread 3.60GHzx8
HD(os) LSI MR9260-8i 120GBx2
HD(data) Seagate 8T(Ext enclosure, raid 0, USB3.0)

Software

Type Name Ver
OS CentOS 7.0
Compiler Gcc 6.1
JavaScript node.js 4.5.0
IDE atom 1.8.0
C++ library boost 1.6.1

Code style

The C++ code follows this google style
If you have difficulty with this site, here is the local resource.

Comments

Doxygen style comments will be applied.

Define & typedef

Here are some predefined type, macros:

namespace bst = boost;
namespace bfs = boost::filesystem;
namespace js = v8;

using u8    =   unsigned char;
using byte  =   unsigned char;
using u16   =   unsigned short;
using i16   =   signed short;
using u32   =   unsigned int;
using i32   =   signed int;
using u64   =   unsigned long long;
using i64   =   signed long long;
using u128  =   unsigned __int128;
using i128  =   signed __int128;

#define DECL_CB(name, ...) \
        using callback_ ## name = bst::function<void(__VA_ARGS__)>; \
        using cb_ ## name ## _t = callback_ ## name;

template<typename T>
using vec_t = std::vector<T>;

template<typename T>
using list_t = std::list<T>;

template<typename T>
using shared_t = std::shared_ptr<T>;

template<typename K, typename T>
using map_t = std::unordered_map<K, T>;

using uuid_t = buid::uuid;

using shared_mtx_t = bst::shared_mutex;
using slock_t = bst::upgrade_lock<bst::shared_mutex>;
using ulock_t = bst::upgrade_to_unique_lock<bst::shared_mutex>;

Errors

A script is used for generate errors automatically from a template, which is a plaint text and is easy to change.
The script:

#!/bin/bash

input="error.def"
output="error.hpp"

date=$(date +%d/%b/%Y)
header="
/*!\n
\t\\\file\terror.hpp\n
\t\\\brief\tDefine error and according messages.\n
\t\\\note\tDo NOT modify this file manually, it's automatically\n
\t\t\tgenerated by script. Instead, you should modify the error.def\n
\t\t\tand re-run gen-err.sh to apply the change.\n
\t\\\author\tHailing.Zhou\n
\t\\\date\t$date\n
*/\n
//////////////////////////////////////////////////////////\n
// CAUTION:\n
// DO NOT modify this file manually.\n
// This file is automatically generated by scripts.\n
//////////////////////////////////////////////////////////\n
#ifndef ERROR_HPP\n
#define ERROR_HPP\n
\n
namespace hhcloud {\n
\t#define _ERR(name)    HC_ERROR_## name\n
\t#define _ERR_STR(name) ErrorToString(_ERR(name))\n"

footer="\n
} // ns hhcloud\n
\n
#endif // ERROR_HPP\n"

func_beg="\n\tstatic const char* ErrorToString(int err) {\n
\t\tswitch(err) {"

func_end="\t\t\tdefault: return \"Unknown error\";\n
\t\t} // End of switch\n
\t} // End of ErrorToString\n"

gen_content() {
  declare -a def_array
  declare -a case_array
  count=0
  err_code=-8000
  err_name=

  while IFS='' read -r line || [[ -n "$line" ]]; do
        line=$(echo $line)
        [[ ${#line} -eq 0 ]] && continue

        if echo $line | grep -qE "^\/\/.*"; then
            def_array[$count]="\n\t$line"
            case_array[$count]="\n\t\t\t$line"

            count=$((count + 1))
        else
            if echo $line | grep -qiE "^code=\d*"; then
                err_code=${line#*=}
            else
            err_name=${line%:*}
            err_msg=${line#*:}

                # If error message not defined, use error name as the message
                if [ "$err_msg" == "" ]; then
                    err_msg=$err_name
                fi

            def_array[$count]="\tconst int _ERR($err_name) = $err_code;"
            case_array[$count]="\t\t\tcase _ERR($err_name): return \"$err_msg\";"

            err_code=$((err_code - 1))
            count=$((count + 1))
            fi
        fi
  done < $input

  n=0
  while [[ $n -lt $count ]]; do
    echo -e ${def_array[$n]} >> $output
    n=$((n + 1))
  done

  # output function
  echo -e $func_beg >> $output

  n=0
  while [[ $n -lt $count ]]; do
        echo -e ${case_array[$n]} >> $output
    n=$((n + 1))
  done

  # end of function
  echo -e $func_end >> $output
}

gen_err() {
  echo "" > $output
  echo -e $header >> $output

  gen_content

  echo -e $footer >> $output
}

echo "Generate $output"
gen_err

A sample of error template:

Code=0
NOERROR:

// Common error, begins from -8000
FAIL:
INVALID:
INTERNAL:
NOT_EXIST:
NOT_FOUND:
NOT_IMPL:
ALLOC_MEM:

// File operation, starts from -8100
Code=-8100
NOT_OPEN:
FAIL_OPEN:

// Haystack Volume, starts from -8200
Code=-8200
VOL_READ_BLOCK:
VOL_CACHE_FULL:
VOL_INVALID_FILE:
VOL_INVALID_VOL:
VOL_INVALID_BLOCK_HEADER:
VOL_INVALID_BLOCK_FOOTER:
VOL_EXCEED_BLOCKS:
VOL_EXCEED_SIZE:
VOL_NONIDENTICAL:
VOL_FAILED_TO_WRITE:
VOL_FAILED_TO_UNLINK:

// Command library, starts from -8300
Code=-8300
CMD_LIB_LOADED:
CMD_LIB_LOAD:
CMD_LIB_UNLOAD:
CMD_LIB_NO_FUNC:
CMD_LIB_INVALID_CMD:

// Command, starts from -8400
Code=-8400
CMD_NO_SRC:
CMD_INVALID_SRC:

// Text Render, starts from -8500
Code=-8500
CMD_TXT_EMPTY:
CMD_TXT_NO_FONT:
CMD_TXT_INIT_FONT:
CMD_TXT_READ_FONT:
CMD_TXT_INIT_RENDER:
CMD_TXT_RENDER_TXT:

// Add more errors here

// End of errors

The generated error.hpp looks like:


/*!
    \file   error.hpp
    \brief  Define error and according messages.
    \note   Do NOT modify this file manually, it's automatically
            generated by script. Instead, you should modify the error.def
            and re-run gen-err.sh to apply the change.
    \author igame
    \date   07/Sep/2016
 */
 //////////////////////////////////////////////////////////
 // CAUTION:
 // DO NOT modify this file manually.
 // This file is automatically generated by scripts.
 //////////////////////////////////////////////////////////
 #ifndef ERROR_HPP
 #define ERROR_HPP

 namespace hhcloud {
    #define _ERR(name) HC_ERROR_## name
    #define _ERR_STR(name) ErrorToString(_ERR(name))

    const int _ERR(NOERROR) = 0;

    // Common error, begins from -8000
    const int _ERR(FAIL) = -1;
    const int _ERR(INVALID) = -2;
    const int _ERR(INTERNAL) = -3;
    const int _ERR(NOT_EXIST) = -4;
    const int _ERR(NOT_FOUND) = -5;
    const int _ERR(NOT_IMPL) = -6;
    const int _ERR(ALLOC_MEM) = -7;

    // File operation, starts from -8100
    const int _ERR(NOT_OPEN) = -8100;
    const int _ERR(FAIL_OPEN) = -8101;

    // Haystack Volume, starts from -8200
    const int _ERR(VOL_READ_BLOCK) = -8200;
    const int _ERR(VOL_CACHE_FULL) = -8201;
    const int _ERR(VOL_INVALID_FILE) = -8202;
    const int _ERR(VOL_INVALID_VOL) = -8203;
    const int _ERR(VOL_INVALID_BLOCK_HEADER) = -8204;
    const int _ERR(VOL_INVALID_BLOCK_FOOTER) = -8205;
    const int _ERR(VOL_EXCEED_BLOCKS) = -8206;
    const int _ERR(VOL_EXCEED_SIZE) = -8207;
    const int _ERR(VOL_NONIDENTICAL) = -8208;
    const int _ERR(VOL_FAILED_TO_WRITE) = -8209;
    const int _ERR(VOL_FAILED_TO_UNLINK) = -8210;

    // Command library, starts from -8300
    const int _ERR(CMD_LIB_LOADED) = -8300;
    const int _ERR(CMD_LIB_LOAD) = -8301;
    const int _ERR(CMD_LIB_UNLOAD) = -8302;
    const int _ERR(CMD_LIB_NO_FUNC) = -8303;
    const int _ERR(CMD_LIB_INVALID_CMD) = -8304;

    // Command, starts from -8400
    const int _ERR(CMD_NO_SRC) = -8400;
    const int _ERR(CMD_INVALID_SRC) = -8401;

    // Text Render, starts from -8500
    const int _ERR(CMD_TXT_EMPTY) = -8500;
    const int _ERR(CMD_TXT_NO_FONT) = -8501;
    const int _ERR(CMD_TXT_INIT_FONT) = -8502;
    const int _ERR(CMD_TXT_READ_FONT) = -8503;
    const int _ERR(CMD_TXT_INIT_RENDER) = -8504;
    const int _ERR(CMD_TXT_RENDER_TXT) = -8505;

    // Add more errors here

    // End of errors

    static const char* ErrorToString(int err) {
        switch(err) {
            case _ERR(NOERROR): return "NOERROR";

            // Common error, begins from -8000
            case _ERR(FAIL): return "FAIL";
            case _ERR(INVALID): return "INVALID";
            case _ERR(INTERNAL): return "INTERNAL";
            case _ERR(NOT_EXIST): return "NOT_EXIST";
            case _ERR(NOT_FOUND): return "NOT_FOUND";
            case _ERR(NOT_IMPL): return "NOT_IMPL";
            case _ERR(ALLOC_MEM): return "ALLOC_MEM";

            // File operation, starts from -8100
            case _ERR(NOT_OPEN): return "NOT_OPEN";
            case _ERR(FAIL_OPEN): return "FAIL_OPEN";

            // Haystack Volume, starts from -8200
            case _ERR(VOL_READ_BLOCK): return "VOL_READ_BLOCK";
            case _ERR(VOL_CACHE_FULL): return "VOL_CACHE_FULL";
            case _ERR(VOL_INVALID_FILE): return "VOL_INVALID_FILE";
            case _ERR(VOL_INVALID_VOL): return "VOL_INVALID_VOL";
            case _ERR(VOL_INVALID_BLOCK_HEADER): return "VOL_INVALID_BLOCK_HEADER";
            case _ERR(VOL_INVALID_BLOCK_FOOTER): return "VOL_INVALID_BLOCK_FOOTER";
            case _ERR(VOL_EXCEED_BLOCKS): return "VOL_EXCEED_BLOCKS";
            case _ERR(VOL_EXCEED_SIZE): return "VOL_EXCEED_SIZE";
            case _ERR(VOL_NONIDENTICAL): return "VOL_NONIDENTICAL";
            case _ERR(VOL_FAILED_TO_WRITE): return "VOL_FAILED_TO_WRITE";
            case _ERR(VOL_FAILED_TO_UNLINK): return "VOL_FAILED_TO_UNLINK";

            // Command library, starts from -8300
            case _ERR(CMD_LIB_LOADED): return "CMD_LIB_LOADED";
            case _ERR(CMD_LIB_LOAD): return "CMD_LIB_LOAD";
            case _ERR(CMD_LIB_UNLOAD): return "CMD_LIB_UNLOAD";
            case _ERR(CMD_LIB_NO_FUNC): return "CMD_LIB_NO_FUNC";
            case _ERR(CMD_LIB_INVALID_CMD): return "CMD_LIB_INVALID_CMD";

            // Command, starts from -8400
            case _ERR(CMD_NO_SRC): return "CMD_NO_SRC";
            case _ERR(CMD_INVALID_SRC): return "CMD_INVALID_SRC";

            // Text Render, starts from -8500
            case _ERR(CMD_TXT_EMPTY): return "CMD_TXT_EMPTY";
            case _ERR(CMD_TXT_NO_FONT): return "CMD_TXT_NO_FONT";
            case _ERR(CMD_TXT_INIT_FONT): return "CMD_TXT_INIT_FONT";
            case _ERR(CMD_TXT_READ_FONT): return "CMD_TXT_READ_FONT";
            case _ERR(CMD_TXT_INIT_RENDER): return "CMD_TXT_INIT_RENDER";
            case _ERR(CMD_TXT_RENDER_TXT): return "CMD_TXT_RENDER_TXT";

            // Add more errors here

            // End of errors
            default: return "Unknown error";
        } // End of switch
    } // End of ErrorToString
 } // ns hhcloud

 #endif // ERROR_HPP

Wrapper of Async File Access

There a dozen of file relative functions in libuv, but we only concentrate on functions about stating and read/write. Let code talk:

/*!
   \brief Asyncronous file read/write class, which hides the details of libuv and expose it as C++ style.
*/
class AsyncFile {
public:
    /** A copy of uv_time_t */
    typedef struct {
        i64 sec;
        i64 nsec;
    } time_type;

    /** File attributes */
    typedef struct {
        u64 dev;
        u64 mode;
        u64 nlink;
        u64 uid;
        u64 gid;
        u64 rdev;
        u64 ino;
        u64 size;
        u64 blksize;
        u64 blocks;
        u64 flags;
        u64 gen;
        time_type atim;
        time_type mtim;
        time_type ctim;
        time_type birthtim;
    } file_stat_t;

    DECL_CB(stat, int /*!< Error code: 0 succeeded; otherwise failed. */, const file_stat_t& /*!< Set of file attributes if succeded. */, void* /*!< User data */)
    DECL_CB(open, int /*!< Error code. */, void* /*!< User data */)
    DECL_CB(read, int /*!< Error code. */, shared_t<vec_t<char>>& /*!< Fetched data if succeeded. */, void* /*!< User data */)
    DECL_CB(write, int, u64 /*!< Number of bytes writed. */, void* /*!< User data */)
    DECL_CB(close, int, void*  /*!< User data */)


private:
    /** callback information which will be used to bring necessary into libuv callback */
    template< class T >
    struct callback_info_t{
        typedef T   callback_type;
        AsyncFile*  sender;
        shared_t<vec_t<char>>   data;
        uv_buf_t    buf;
        void*   user_data;
        callback_type cb;
    }; // callback_info_t;

    using ci_read_t = callback_info_t<cb_read_t>;
    using ci_write_t = callback_info_t<cb_write_t>;

public:
    AsyncFile() {}
    AsyncFile(const AsyncFile&) = delete;

    ~AsyncFile() {
        Close();
    }
    /*!
       \brief open a file for asynchronous operation.
       \return None
    */
    void Open(const std::string& path /*!< File path. */, const cb_open_t& cb /*!< Callback function. */, void* user_data = nullptr /*!< Extra user data which will be passed to callback */) {
        uv_fs_t* req = this->CreateReq<cb_open_t>(cb, user_data);

        uv_fs_open(uv_default_loop(), req, path.c_str(), O_CREAT | O_RDWR, 0, AsyncFile::OnOpen);
    }

    /*!
       \brief close opened file.
       \return None.
    */
    void Close(const cb_close_t& cb = [&](int, void*) { return; }/*!< Callback function */, void* user_data = nullptr /*!< Extra user data which will be passed to callback */) {
        if (m_fd > 0) {
            uv_fs_t* req = this->CreateReq<cb_close_t>(cb, user_data);

            uv_fs_close(uv_default_loop(), req, m_fd, AsyncFile::OnClose);
        } else {
            cb(_ERR(NOT_OPEN), user_data);
        }
    }

    /*!
       \brief stat a file.
       \return None.
    */
    void Stat(const std::string& path /*!< File path. */, const cb_stat_t& cb /*!< Callback function */, void* user_data = nullptr  /*!< Extra user data which will be passed to callback */) {
        uv_fs_t* req = this->CreateReq<cb_stat_t>(cb, user_data);

        uv_fs_stat(uv_default_loop(), req, path.c_str(), AsyncFile::OnStat);
    }

    /*!
       \brief read an amount of data from opened file begining at specified offset.
       \return None.
    */
    void Read(u64 offset, size_t size, const cb_read_t& cb, void* user_data = nullptr  /*!< Extra user data which will be passed to callback */) {
        assert(m_fd != 0);

        auto req = this->CreateReq<cb_read_t>(cb, user_data, size);
        auto ci = reinterpret_cast<ci_read_t *>(req->data);

        uv_fs_read(uv_default_loop(), req, m_fd, &ci->buf, 1, offset, AsyncFile::OnRead);
    }

    /*!
       \brief write data into file from specified offset.
    */
    void Write(u64 offset, shared_t<vec_t<char>>& array, const cb_write_t& cb, void* user_data = nullptr  /*!< Extra user data which will be passed to callback */) {
        assert(m_fd != 0);

        auto req = this->CreateReq<cb_write_t>(cb, user_data, array);
        auto ci = reinterpret_cast<ci_write_t *>(req->data);

        uv_fs_write(uv_default_loop(), req, m_fd, &ci->buf, 1, offset, AsyncFile::OnWrite);
    }

private:
    /*!
       \brief Create a uv_fs_t object.
       \return Pointer to uv_fs_t object.
    */
    template< class T>
    inline uv_fs_t* CreateReq(const T& cb, void* user_data) {
        uv_fs_t* req = (uv_fs_t *)malloc(sizeof(uv_fs_t));
        std::memset(req, 0x0, sizeof(uv_fs_t));

        callback_info_t<T>* ci = new callback_info_t<T>;

        ci->sender = this;
        ci->cb = cb;
        ci->user_data = user_data;

        req->data = ci;

        return req;
    }

    template<class T>
    inline uv_fs_t* CreateReq(const T& cb, void* user_data, size_t size) {
        auto req = CreateReq<T>(cb, user_data);
        auto ci = reinterpret_cast<callback_info_t<T> *>(req->data);

        ci->data = mk_shared<vec_t<char>>();
        ci->data->resize(size);

        ci->buf.base = &(*ci->data)[0];
        ci->buf.len = ci->data->size();

        return req;
    }

    template<class T>
    inline uv_fs_t* CreateReq(const T& cb, void* user_data, shared_t<vec_t<char>>& svec) {
        auto req = CreateReq<T>(cb, user_data);
        auto ci = reinterpret_cast<callback_info_t<T> *>(req->data);

        ci->data = svec;
        ci->buf.base = &(*ci->data)[0];
        ci->buf.len = ci->data->size();

        return req;
    }

    /*!
       \brief Release created uv_fs_t object.
    */
    template< class T >
    inline void DelReq(uv_fs_t* req) {
        auto ci = reinterpret_cast<callback_info_t<T> *>(req->data);

        delete ci;
        uv_fs_req_cleanup(req);
    }

private:
    /*!
       \brief Callback for uv_fs_stat.
    */
    static void OnStat(uv_fs_t* req) {
        auto ci = reinterpret_cast<callback_info_t<cb_stat_t> *>(req->data);

        if (req->result < 0) {
            int err = req->result;
            ci->cb(err, {}, ci->user_data);
        } else {
            auto ft = reinterpret_cast<file_stat_t *>(&req->statbuf);

            ci->cb(0, *ft, ci->user_data);
        }

        ci->sender->DelReq<cb_stat_t>(req);
    }

    /*!
       \brief Callback for uv_fs_open.
    */
    static void OnOpen(uv_fs_t* req) {
        auto ci = reinterpret_cast<callback_info_t<cb_open_t> *>(req->data);

        if (req->result < 0) {
            ci->cb(req->result, ci->user_data);
        } else {
            ci->sender->m_fd = req->result;
            ci->cb(0, ci->user_data);
        }

        ci->sender->DelReq<cb_open_t>(req);
    }

    /*!
       \brief Callback for uv_fs_close
    */
    static void OnClose(uv_fs_t* req) {
        auto ci = reinterpret_cast<callback_info_t<cb_close_t> *>(req->data);

        if (req->result < 0) {
            ci->cb(req->result, ci->user_data);
        } else {
            ci->cb(0, ci->user_data);
        }

        ci->sender->DelReq<cb_close_t>(req);
        // ci->sender->m_fd = 0;
    }

    /*!
       \brief Callback for uv_fs_read
    */
    static void OnRead(uv_fs_t* req) {
        auto ci = reinterpret_cast<callback_info_t<cb_read_t> *>(req->data);

        if (req->result < 0) {
            auto temp = mk_shared<vec_t<char>>();

            ci->cb(req->result, temp, ci->user_data);
        } else {
            if (req->result == 0) {
                auto temp = mk_shared<vec_t<char>>();

                ci->cb(0, temp, ci->user_data);
            } else {
                if (req->result != ci->data->size()) {
                    auto temp = mk_shared<vec_t<char>>();

                    ci->cb(_ERR(FAIL), temp, ci->user_data);
                } else {
                    ci->cb(0, ci->data, ci->user_data);
                }
            }
        }

        ci->sender->DelReq<cb_read_t>(req);
    }

    /*!
       \brief Callback for uv_fs_write.
    */
    static void OnWrite(uv_fs_t* req) {
        auto ci = reinterpret_cast<callback_info_t<cb_write_t> *>(req->data);

        if (req->result < 0) {
            ci->cb(req->result, 0, ci->user_data);
        } else {
            ci->cb(0, req->result, ci->user_data);
        }

        ci->sender->DelReq<cb_write_t>(req);
    }
private:
    uv_file m_fd = 0; /*!< uv file handle. */
}; // class AsyncFile

Wrapper of Async Work Queue

libuv uses work queue to perform asynchronous operation. More details can be found in libuv’s official documents.
The declaration of AsyncWork looks like:

/*!
     \brief C++ wrapper for libuv asynchronous queue work.
     \note The template parameter T is the type of user data which will be used for user's extra data when posting work item into queue.
 */
 template<class T>
 class AsyncWorker {
 public:
      DECL_CB(work, T* /*!< User data. */)
      DECL_CB(after_work, T* /*!< User data. */, bool /*!< Cancelation flag */)

 private:
      typedef struct {
            AsyncWorker* sender;
            cb_work_t cb_work;
            cb_after_work_t cb_after_work;
            T* user_data;
      } callback_info_t;

      using ci_t = callback_info_t;

 public:
      /*!
          \brief Post work request to queue.
          \return None.
      */
      inline void post(T* user_data /*!< User data. */, const cb_work_t& cb_work /*!< Callback when work is been processing. */, const cb_after_work_t& cb_afterWork /*!< Callback when work has been done. */) {
            uv_work_t* req = createReq(user_data, cb_work, cb_afterWork);

            uv_queue_work(uv_default_loop(), req, AsyncWorker::onWork, AsyncWorker::onAfterWork);
      }

 private:
      /*!
          \brief Create work request.
          \return uv_work_t* libuv work request type.
      */
      inline uv_work_t* createReq(T* user_data /*!< User data. */, const cb_work_t& cb_work /*!< Callback when work is been processing. */, const cb_after_work_t& cb_afterWork /*!< Callback when work is done. */) {
            uv_work_t* req = (uv_work_t *)malloc(sizeof(uv_work_t));
            std::memset(req, 0x0, sizeof(uv_work_t));

            auto ci = new ci_t{ this, cb_work, cb_afterWork, user_data };

            req->data = ci;

            return req;
      }

      /*!
          \brief Delete work request.
          \return None.
      */
      inline void delReq(uv_work_t* req /*!< libuv work struct. */) {
            auto ci = reinterpret_cast<ci_t *>(req->data);

            delete ci;
            free(req);
      }

      /*!
          \brief Event handle for libuv work processing.
          \return None.
      */
      static void onWork(uv_work_t* req /*!< Libuv work struct. */) {
            auto ci = reinterpret_cast<ci_t *>(req->data);

            ci->cb_work(ci->user_data);
      }

      /*!
          \brief Event handle for libuv work done.
          \return None.
      */
      static void onAfterWork(uv_work_t* req /*!< libuv work struct. */, int status /*!< status of work. \note Now only UV_ECANCELED is cared about. */) {
            auto ci = reinterpret_cast<ci_t *>(req->data);

            ci->cb_after_work(ci->user_data, status == UV_ECANCELED);

            ci->sender->delReq(req);
      }
 }; // class AsyncWorker

Wrapper of Timer

Make the libuv timer featurn more easily to use.

/*!
     \brief C++ wrapper for libuv timer
 */
class AsyncTimer {
public:
    /** Timer event callback. */
    DECL_CB(timer, void)

    const u64 INTERVAL_MIN = 1; /*!< Minimum interval for timer: 1 Millisecond. */
    const u64 DEFAULT_INTERVAL = 1000;  /*!< Default interval: 1 second. */

private:
    //** Callback information. */
    typedef struct {
        AsyncTimer* sender;
        cb_timer_t cb;
    } callback_info_t;

    using ci_t = callback_info_t;

 public:
    AsyncTimer() {
        Init();
    }

    AsyncTimer(const AsyncTimer&) = delete;

    ~AsyncTimer() {
        Stop();
        assert(m_handle != nullptr);
        free(m_handle);
    }

    /*!
      \brief Check if the timer already started.
      \return bool true/false.
    */
    inline bool IsStarted() {
        return m_started;
    }

    /*!
      \brief Set interval, unit: millisecond.
      \note  interval must equal or be greater than INTERVAL_MIN.
      \return None.
    */
    inline void SetInterval(u64 msec) {
        uv_timer_set_repeat(m_handle, msec < INTERVAL_MIN ? INTERVAL_MIN : msec);
    }

    /*!
      \brief Get interval.
      \return u64 interval value.
    */
    inline u64 GetInterval() {
        return uv_timer_get_repeat(m_handle);
    }

    /*!
      \brief Start timer. When the specified interval arrives, specified callback(defined by cb_timer_t) will be called.
      \return None.
    */
    inline void Start(const cb_timer_t& cb) {
        if (IsStarted()) return;

        m_handle->data = new ci_t { this, cb };
        uv_timer_start(m_handle, AsyncTimer::OnTimer, 0, GetInterval());

        m_started = true;
    }

    /*!
      \brief Stop timer.
      \return None.
    */
    inline void Stop() {
        if (IsStarted()) {
            m_started = false;

            auto ci = reinterpret_cast<ci_t *>(m_handle->data);
            delete ci;

            m_handle->data = nullptr;
            uv_timer_stop(m_handle);

            TRACE("Timer stopped");
        }
    }

 private:
    /*!
      \brief Initialize timer handler.
      \return None.
    */
    inline void Init() {
        m_handle = (uv_timer_t *)malloc(sizeof(uv_timer_t));
        uv_timer_init(uv_default_loop(), m_handle);
        m_handle->data = nullptr;
        m_started = false;
    }

    /*!
      \brief Timer handling event for libuv.
      \return None.
    */
    static void OnTimer(uv_timer_t* handle) {
        auto ci = reinterpret_cast<ci_t *>(handle->data);

        if (ci->sender->IsStarted()) {
            ci->cb();
        }
    }

private:
    uv_timer_t* m_handle = nullptr; /*!< Timer handle */
    bst::atomic_bool m_started; /*!< Flag of if timer started/stopped */
 }; // class AsyncTimer
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章