view libirccd-js/irccd/mod-timer.cpp @ 374:e9adab218027

Misc: various code cleanup
author David Demelier <markand@malikania.fr>
date Thu, 08 Dec 2016 20:06:38 +0100
parents 15da984ce7ea
children c6fbb6e0e06d
line wrap: on
line source

/*
 * mod-timer.cpp -- Irccd.Timer API
 *
 * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <format.h>

#include "irccd.hpp"
#include "logger.hpp"
#include "mod-irccd.hpp"
#include "mod-timer.hpp"
#include "mod-plugin.hpp"
#include "plugin-js.hpp"
#include "timer.hpp"

using namespace fmt::literals;

namespace irccd {

namespace {

const char *Signature("\xff""\xff""irccd-timer-ptr");
const char *CallbackTable("\xff""\xff""irccd-timer-callbacks");

void handleSignal(std::weak_ptr<JsPlugin> ptr, std::string key)
{
    auto plugin = ptr.lock();

    if (!plugin)
        return;

    auto &irccd = dukx_get_irccd(plugin->context());

    irccd.post([plugin, key] (Irccd &) {
        StackAssert sa(plugin->context());

        duk_get_global_string(plugin->context(), CallbackTable);
        duk_get_prop_string(plugin->context(), -1, key.c_str());
        duk_remove(plugin->context(), -2);

        if (duk_is_callable(plugin->context(), -1)) {
            if (duk_pcall(plugin->context(), 0) != 0)
                log::warning("plugin {}: {}"_format(plugin->name(), dukx_exception(plugin->context(), -1).stack));
            else
                duk_pop(plugin->context());
        } else
            duk_pop(plugin->context());
    });
}

std::shared_ptr<Timer> self(duk_context *ctx)
{
    StackAssert sa(ctx);

    duk_push_this(ctx);
    duk_get_prop_string(ctx, -1, Signature);
    auto ptr = duk_to_pointer(ctx, -1);
    duk_pop_2(ctx);

    if (!ptr)
        duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Timer object");

    return *static_cast<std::shared_ptr<Timer> *>(ptr);
}

/*
 * Method: Timer.start()
 * --------------------------------------------------------
 *
 * Start the timer. If the timer is already started the method is a no-op.
 */
duk_ret_t start(duk_context *ctx)
{
    auto timer = self(ctx);

    if (!timer->isRunning())
        timer->start();

    return 0;
}

/*
 * Method: Timer.stop()
 * --------------------------------------------------------
 *
 * Stop the timer.
 */
duk_ret_t stop(duk_context *ctx)
{
    auto timer = self(ctx);

    if (timer->isRunning())
        timer->stop();

    return 0;
}

const duk_function_list_entry methods[] = {
    { "start",  start,      0 },
    { "stop",   stop,       0 },
    { nullptr,  nullptr,    0 }
};

/*
 * Function: Irccd.Timer(type, delay, callback) [constructor]
 * --------------------------------------------------------
 *
 * Create a new timer object.
 *
 * Arguments:
 *   - type, the type of timer (Irccd.Timer.Single or Irccd.Timer.Repeat),
 *   - delay, the interval in milliseconds,
 *   - callback, the function to call.
 */
duk_ret_t constructor(duk_context *ctx)
{
    // Check parameters.
    auto type = duk_require_int(ctx, 0);
    auto delay = duk_require_int(ctx, 1);

    if (type < static_cast<int>(TimerType::Single) || type > static_cast<int>(TimerType::Repeat))
        duk_error(ctx, DUK_ERR_TYPE_ERROR, "invalid timer type");
    if (delay < 0)
        duk_error(ctx, DUK_ERR_TYPE_ERROR, "negative delay given");
    if (!duk_is_callable(ctx, 2))
        duk_error(ctx, DUK_ERR_TYPE_ERROR, "missing callback function");

    // Construct the timer in 'this'.
    auto timer = std::make_shared<Timer>(static_cast<TimerType>(type), delay);
    auto hash = std::to_string(reinterpret_cast<std::uintptr_t>(timer.get()));

    timer->onSignal.connect(std::bind(handleSignal, std::weak_ptr<JsPlugin>(dukx_get_plugin(ctx)), hash));

    duk_push_this(ctx);
    duk_push_pointer(ctx, new std::shared_ptr<Timer>(std::move(timer)));
    duk_put_prop_string(ctx, -2, Signature);
    duk_push_string(ctx, hash.c_str());
    duk_put_prop_string(ctx, -2, "\xff""\xff""timer-key");
    duk_push_c_function(ctx, [] (duk_context *ctx) -> duk_ret_t {
        StackAssert sa(ctx);

        duk_get_prop_string(ctx, 0, "\xff""\xff""timer-key");
        auto hash = duk_get_string(ctx, -1);
        duk_pop(ctx);
        duk_get_prop_string(ctx, 0, Signature);
        static_cast<std::shared_ptr<Timer> *>(duk_to_pointer(ctx, -1))->get()->stop();
        delete static_cast<std::shared_ptr<Timer> *>(duk_to_pointer(ctx, -1));
        duk_pop(ctx);
        duk_get_global_string(ctx, CallbackTable);
        duk_del_prop_string(ctx, -1, hash);
        duk_pop(ctx);
        log::debug("plugin: timer destroyed");

        return 0;
    }, 1);
    duk_set_finalizer(ctx, -2);

    // Save a callback function into the callback table.
    duk_get_global_string(ctx, CallbackTable);
    duk_dup(ctx, 2);
    duk_put_prop_string(ctx, -2, hash.c_str());
    duk_pop(ctx);

    return 0;
}

const duk_number_list_entry constants[] = {
    { "Single",     static_cast<int>(TimerType::Single) },
    { "Repeat",     static_cast<int>(TimerType::Repeat) },
    { nullptr,      0                                   }
};

} // !namespace

TimerModule::TimerModule() noexcept
    : Module("Irccd.Timer")
{
}

void TimerModule::load(Irccd &, std::shared_ptr<JsPlugin> plugin)
{
    StackAssert sa(plugin->context());

    duk_get_global_string(plugin->context(), "Irccd");
    duk_push_c_function(plugin->context(), constructor, 3);
    duk_put_number_list(plugin->context(), -1, constants);
    duk_push_object(plugin->context());
    duk_put_function_list(plugin->context(), -1, methods);
    duk_put_prop_string(plugin->context(), -2, "prototype");
    duk_put_prop_string(plugin->context(), -2, "Timer");
    duk_pop(plugin->context());
    duk_push_object(plugin->context());
    duk_put_global_string(plugin->context(), CallbackTable);
}

} // !irccd