OpenRCT2/src/openrct2/scripting/Duktape.hpp

434 lines
12 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#pragma once
#ifdef ENABLE_SCRIPTING
# include "../core/Console.hpp"
# include "../core/EnumMap.hpp"
# include "../ride/Vehicle.h"
# include "../world/Map.h"
# include <cstdio>
# include <dukglue/dukglue.h>
# include <duktape.h>
# include <optional>
# include <stdexcept>
namespace OpenRCT2::Scripting
{
template<typename T> DukValue GetObjectAsDukValue(duk_context* ctx, const std::shared_ptr<T>& value)
{
dukglue::types::DukType<std::shared_ptr<T>>::template push<T>(ctx, value);
return DukValue::take_from_stack(ctx);
}
template<typename T> T AsOrDefault(const DukValue& value, const T& defaultValue = {}) = delete;
inline std::string AsOrDefault(const DukValue& value, std::string_view defaultValue)
{
return value.type() == DukValue::STRING ? value.as_string() : std::string(defaultValue);
}
template<> inline std::string AsOrDefault(const DukValue& value, const std::string& defaultValue)
{
return value.type() == DukValue::STRING ? value.as_string() : defaultValue;
}
template<> inline int32_t AsOrDefault(const DukValue& value, const int32_t& defaultValue)
{
return value.type() == DukValue::NUMBER ? value.as_int() : defaultValue;
}
template<> inline bool AsOrDefault(const DukValue& value, const bool& defaultValue)
{
return value.type() == DukValue::BOOLEAN ? value.as_bool() : defaultValue;
}
enum class DukUndefined
{
};
constexpr DukUndefined undefined{};
/**
* Allows creation of an object on the duktape stack and setting properties on it before
* retrieving the DukValue instance of it.
*/
class DukObject
{
private:
duk_context* _ctx{};
duk_idx_t _idx = DUK_INVALID_INDEX;
public:
DukObject(duk_context* ctx)
: _ctx(ctx)
{
}
DukObject(const DukObject&) = delete;
DukObject(DukObject&& m) noexcept
{
_ctx = m._ctx;
_idx = m._idx;
m._ctx = {};
m._idx = {};
}
~DukObject()
{
PopObjectIfExists();
}
void Set(const char* name, std::nullptr_t)
{
EnsureObjectPushed();
duk_push_null(_ctx);
duk_put_prop_string(_ctx, _idx, name);
}
void Set(const char* name, DukUndefined)
{
EnsureObjectPushed();
duk_push_undefined(_ctx);
duk_put_prop_string(_ctx, _idx, name);
}
void Set(const char* name, bool value)
{
EnsureObjectPushed();
duk_push_boolean(_ctx, value);
duk_put_prop_string(_ctx, _idx, name);
}
void Set(const char* name, int32_t value)
{
EnsureObjectPushed();
duk_push_int(_ctx, value);
duk_put_prop_string(_ctx, _idx, name);
}
void Set(const char* name, uint32_t value)
{
EnsureObjectPushed();
duk_push_uint(_ctx, value);
duk_put_prop_string(_ctx, _idx, name);
}
void Set(const char* name, int64_t value)
{
EnsureObjectPushed();
duk_push_number(_ctx, value);
duk_put_prop_string(_ctx, _idx, name);
}
void Set(const char* name, uint64_t value)
{
EnsureObjectPushed();
duk_push_number(_ctx, value);
duk_put_prop_string(_ctx, _idx, name);
}
void Set(const char* name, std::string_view value)
{
EnsureObjectPushed();
duk_push_lstring(_ctx, value.data(), value.size());
duk_put_prop_string(_ctx, _idx, name);
}
void Set(const char* name, const char* value)
{
Set(name, std::string_view(value));
}
void Set(const char* name, const DukValue& value)
{
EnsureObjectPushed();
value.push();
duk_put_prop_string(_ctx, _idx, name);
}
template<typename T> void Set(const char* name, const std::optional<T>& value)
{
if (value)
{
EnsureObjectPushed();
duk_push_null(_ctx);
duk_put_prop_string(_ctx, _idx, name);
}
else
{
Set(name, *value);
}
}
DukValue Take()
{
EnsureObjectPushed();
auto result = DukValue::take_from_stack(_ctx, _idx);
_idx = DUK_INVALID_INDEX;
return result;
}
private:
void PopObjectIfExists()
{
if (_idx != DUK_INVALID_INDEX)
{
duk_remove(_ctx, _idx);
_idx = DUK_INVALID_INDEX;
}
}
void EnsureObjectPushed()
{
if (_idx == DUK_INVALID_INDEX)
{
_idx = duk_push_object(_ctx);
}
}
};
class DukStackFrame
{
private:
duk_context* _ctx{};
duk_idx_t _top;
public:
DukStackFrame(duk_context* ctx)
: _ctx(ctx)
{
_top = duk_get_top(ctx);
}
~DukStackFrame()
{
auto top = duk_get_top(_ctx);
if (top != _top)
{
duk_set_top(_ctx, _top);
_ctx = {};
Console::Error::WriteLine("duktape stack was not returned to original state!");
}
_ctx = {};
}
DukStackFrame(const DukStackFrame&) = delete;
DukStackFrame(DukStackFrame&&) = delete;
};
/**
* Bi-directional map for converting between strings and enums / numbers.
*/
template<typename T> using DukEnumMap = EnumMap<T>;
inline duk_ret_t duk_json_decode_wrapper(duk_context* ctx, void*)
{
duk_json_decode(ctx, -1);
return 1;
}
inline std::optional<DukValue> DuktapeTryParseJson(duk_context* ctx, std::string_view json)
{
duk_push_lstring(ctx, json.data(), json.size());
if (duk_safe_call(ctx, duk_json_decode_wrapper, nullptr, 1, 1) == DUK_EXEC_SUCCESS)
{
return DukValue::take_from_stack(ctx);
}
// Pop error off stack
duk_pop(ctx);
return std::nullopt;
}
std::string ProcessString(const DukValue& value);
template<typename T> DukValue ToDuk(duk_context* ctx, const T& value) = delete;
template<typename T> T FromDuk(const DukValue& s) = delete;
template<> inline DukValue ToDuk(duk_context* ctx, const std::nullptr_t&)
{
duk_push_null(ctx);
return DukValue::take_from_stack(ctx);
}
template<> inline DukValue ToDuk(duk_context* ctx, const DukUndefined&)
{
duk_push_undefined(ctx);
return DukValue::take_from_stack(ctx);
}
template<> inline DukValue ToDuk(duk_context* ctx, const bool& value)
{
duk_push_boolean(ctx, value);
return DukValue::take_from_stack(ctx);
}
template<> inline DukValue ToDuk(duk_context* ctx, const uint8_t& value)
{
duk_push_int(ctx, value);
return DukValue::take_from_stack(ctx);
}
template<> inline DukValue ToDuk(duk_context* ctx, const int32_t& value)
{
duk_push_int(ctx, value);
return DukValue::take_from_stack(ctx);
}
template<> inline DukValue ToDuk(duk_context* ctx, const int64_t& value)
{
duk_push_number(ctx, value);
return DukValue::take_from_stack(ctx);
}
template<> inline DukValue ToDuk(duk_context* ctx, const std::string_view& value)
{
duk_push_lstring(ctx, value.data(), value.size());
return DukValue::take_from_stack(ctx);
}
template<> inline DukValue ToDuk(duk_context* ctx, const std::string& value)
{
return ToDuk(ctx, std::string_view(value));
}
template<size_t TLen> inline DukValue ToDuk(duk_context* ctx, const char (&value)[TLen])
{
duk_push_string(ctx, value);
return DukValue::take_from_stack(ctx);
}
template<typename T> inline DukValue ToDuk(duk_context* ctx, const std::optional<T>& value)
{
return value ? ToDuk(ctx, *value) : ToDuk(ctx, nullptr);
}
template<> CoordsXY inline FromDuk(const DukValue& d)
{
CoordsXY result;
result.x = AsOrDefault(d["x"], 0);
result.y = AsOrDefault(d["y"], 0);
return result;
}
template<> DukValue inline ToDuk(duk_context* ctx, const CoordsXY& coords)
{
DukObject dukCoords(ctx);
dukCoords.Set("x", coords.x);
dukCoords.Set("y", coords.y);
return dukCoords.Take();
}
template<> DukValue inline ToDuk(duk_context* ctx, const ScreenCoordsXY& coords)
{
DukObject dukCoords(ctx);
dukCoords.Set("x", coords.x);
dukCoords.Set("y", coords.y);
return dukCoords.Take();
}
template<> inline DukValue ToDuk(duk_context* ctx, const CoordsXYZ& value)
{
if (value.IsNull())
{
return ToDuk(ctx, nullptr);
}
DukObject dukCoords(ctx);
dukCoords.Set("x", value.x);
dukCoords.Set("y", value.y);
dukCoords.Set("z", value.z);
return dukCoords.Take();
}
template<> inline CoordsXYZ FromDuk(const DukValue& value)
{
CoordsXYZ result;
if (value.type() == DukValue::Type::OBJECT)
{
result.x = AsOrDefault(value["x"], 0);
result.y = AsOrDefault(value["y"], 0);
result.z = AsOrDefault(value["z"], 0);
}
else
{
result.SetNull();
}
return result;
}
template<> inline DukValue ToDuk(duk_context* ctx, const CoordsXYZD& value)
{
if (value.IsNull())
{
return ToDuk(ctx, nullptr);
}
DukObject dukCoords(ctx);
dukCoords.Set("x", value.x);
dukCoords.Set("y", value.y);
dukCoords.Set("z", value.z);
dukCoords.Set("direction", value.direction);
return dukCoords.Take();
}
template<> inline DukValue ToDuk(duk_context* ctx, const GForces& value)
{
DukObject dukGForces(ctx);
dukGForces.Set("lateralG", value.LateralG);
dukGForces.Set("verticalG", value.VerticalG);
return dukGForces.Take();
}
template<> inline CoordsXYZD FromDuk(const DukValue& value)
{
CoordsXYZD result;
if (value.type() == DukValue::Type::OBJECT)
{
result.x = AsOrDefault(value["x"], 0);
result.y = AsOrDefault(value["y"], 0);
result.z = AsOrDefault(value["z"], 0);
result.direction = AsOrDefault(value["direction"], 0);
}
else
{
result.SetNull();
}
return result;
}
template<> inline DukValue ToDuk(duk_context* ctx, const ScreenSize& value)
{
DukObject dukCoords(ctx);
dukCoords.Set("width", value.width);
dukCoords.Set("height", value.height);
return dukCoords.Take();
}
template<> ObjectEntryIndex inline FromDuk(const DukValue& d)
{
if (d.type() == DukValue::Type::NUMBER)
{
auto value = d.as_int();
if (value >= 0 && value <= std::numeric_limits<ObjectEntryIndex>::max())
{
return static_cast<ObjectEntryIndex>(value);
}
}
return OBJECT_ENTRY_INDEX_NULL;
}
} // namespace OpenRCT2::Scripting
#endif