Skip to content

Commit

Permalink
ScConsole now uses QuickJS
Browse files Browse the repository at this point in the history
  • Loading branch information
mrmbernardi committed Jan 19, 2025
1 parent a274c57 commit 4491680
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 41 deletions.
2 changes: 1 addition & 1 deletion src/openrct2/scripting/HookEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ void HookEngine::Call(HOOK_TYPE type, const JSValue arg, bool isGameStateMutable
void HookEngine::Call(
HOOK_TYPE type, const std::initializer_list<std::pair<std::string_view, std::any>>& args, bool isGameStateMutable)
{
//TODO (mber)
// TODO (mber)
throw std::runtime_error("HookEngine::Call() not implemented");
/*
auto& hookList = GetHookList(type);
Expand Down
3 changes: 1 addition & 2 deletions src/openrct2/scripting/HookEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@

#ifdef ENABLE_SCRIPTING_REFACTOR

#include <quickjs.h>

#include <any>
#include <memory>
#include <quickjs.h>
#include <string>
#include <tuple>
#include <vector>
Expand Down
3 changes: 1 addition & 2 deletions src/openrct2/scripting/Plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@

#ifdef ENABLE_SCRIPTING_REFACTOR

#include <quickjs.h>

#include <optional>
#include <quickjs.h>
#include <string>
#include <string_view>
#include <vector>
Expand Down
28 changes: 20 additions & 8 deletions src/openrct2/scripting/ScriptEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,14 +414,17 @@ void ScriptEngine::Initialise()
{
if (_initialised)
throw std::runtime_error("Script engine already initialised.");
_runtime = JS_NewRuntime();
if (!_runtime)
throw "QuickJS: cannot allocate JS runtime\n";
{
_runtime = JS_NewRuntime();
if (!_runtime)
throw "QuickJS: cannot allocate JS runtime\n";

#ifndef NDEBUG
// Dump JS engine memory leaks
JS_SetDumpFlags(_runtime, 0x4000);
// Dump JS engine memory leaks
JS_SetDumpFlags(_runtime, 0x4000);
#endif
}

_replContext = JS_NewContext(_runtime);
if (!_replContext)
Expand All @@ -436,14 +439,24 @@ void ScriptEngine::Initialise()
ClearParkStorage();
}

void ScriptEngine::InitialiseContext(JSContext* ctx)
static void RegisterGlobal(JSContext* ctx, const char* name, JSValue value)
{
JSValue global = JS_GetGlobalObject(ctx);
JS_SetPropertyStr(ctx, global, name, value);
JS_FreeValue(ctx, global);
}

JSRuntime* ScriptEngine::_runtime = nullptr;
ScConsole Scripting::gScConsole;

void ScriptEngine::InitialiseContext(JSContext* ctx) const
{
// TODO (mber) register C functions
// ScCheats::Register(ctx);
// ScClimate::Register(ctx);
// ScClimateState::Register(ctx);
// ScConfiguration::Register(ctx);
// ScConsole::Register(ctx);
gScConsole.Register(ctx);
// ScContext::Register(ctx);
// ScDate::Register(ctx);
// ScDisposable::Register(ctx);
Expand Down Expand Up @@ -493,10 +506,9 @@ void ScriptEngine::InitialiseContext(JSContext* ctx)
// ScMechanic::Register(ctx);
// ScSecurity::Register(ctx);
// ScPlugin::Register(ctx);
//
// dukglue_register_global(ctx, std::make_shared<ScCheats>(), "cheats");
// dukglue_register_global(ctx, std::make_shared<ScClimate>(), "climate");
// dukglue_register_global(ctx, std::make_shared<ScConsole>(_console), "console");
RegisterGlobal(ctx, "console", gScConsole.New(ctx, _console));
// dukglue_register_global(ctx, std::make_shared<ScContext>(_execInfo, _hookEngine), "context");
// dukglue_register_global(ctx, std::make_shared<ScDate>(), "date");
// dukglue_register_global(ctx, std::make_shared<ScMap>(ctx), "map");
Expand Down
49 changes: 47 additions & 2 deletions src/openrct2/scripting/ScriptEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ namespace OpenRCT2::Scripting
private:
InteractiveConsole& _console;
IPlatformEnvironment& _env;
JSRuntime* _runtime = nullptr;
static JSRuntime* _runtime;
JSContext* _replContext = nullptr;
bool _initialised{};
bool _hotReloadingInitialised{};
Expand Down Expand Up @@ -203,7 +203,7 @@ namespace OpenRCT2::Scripting
void SetParkStorageFromJSON(std::string_view value);

void Initialise();
static void InitialiseContext(JSContext* ctx);
void InitialiseContext(JSContext* ctx) const;
void LoadTransientPlugins();
void UnloadTransientPlugins();
void StopUnloadRegisterAllPlugins();
Expand Down Expand Up @@ -288,6 +288,51 @@ namespace OpenRCT2::Scripting
std::string Stringify(JSContext* context, JSValue value);
std::string ProcessString(JSValue value);

class ScBase
{
private:
JSClassID classId = JS_INVALID_CLASS_ID;
virtual const JSClassDef* GetClassDef() = 0;
virtual std::span<const JSCFunctionListEntry> GetClassFuncs() = 0;

protected:
[[nodiscard]] JSValue MakeWithOpaque(JSContext* ctx, void* opaque)
{
JSValue obj = JS_NewObjectClass(ctx, classId);
if (JS_IsException(obj))
throw std::runtime_error("Failed to create new object for class.");
JS_SetOpaque(obj, opaque);

// Note: Usually one would set a class prototype rather than setting the functions as properties on every creation.
// However, that causes the attached functions to not be "own properties" which make them a little less
// visible to the user, and also does not match the previous behaviour with the DukTape engine.
const auto funcs = GetClassFuncs();
JS_SetPropertyFunctionList(ctx, obj, funcs.data(), funcs.size());
return obj;
}

template<typename T>
[[nodiscard]] T GetOpaque(JSValue obj) const
{
return static_cast<T>(JS_GetOpaque(obj, classId));
}

public:
virtual ~ScBase() = default;
void Register(JSContext* ctx)
{
if (classId == JS_INVALID_CLASS_ID)
{
// Note: Technically JS_NewClassID is meant to be called once during the lifetime of the program
// whereas JS_NewClass is meant to be called for each runtime.
// If we ever have any more runtimes, this flow would be wrong.
JSRuntime* rt = JS_GetRuntime(ctx);
JS_NewClassID(rt, &classId);
JS_NewClass(rt, classId, GetClassDef());
}
}
};

} // namespace OpenRCT2::Scripting

#endif
74 changes: 48 additions & 26 deletions src/openrct2/scripting/bindings/game/ScConsole.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,58 +9,80 @@

#pragma once

#ifdef ENABLE_SCRIPTING
#ifdef ENABLE_SCRIPTING_REFACTOR

#include "../../../interface/InteractiveConsole.h"
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"

namespace OpenRCT2::Scripting
{
class ScConsole
class ScConsole;
extern ScConsole gScConsole;

class ScConsole final : public ScBase
{
private:
InteractiveConsole& _console;
static constexpr JSClassDef classDef = { "Console", nullptr, nullptr, nullptr, nullptr };

public:
ScConsole(InteractiveConsole& console)
: _console(console)
static JSValue clear(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
if (const auto console = gScConsole.GetOpaque<InteractiveConsole*>(thisVal))
console->Clear();
return JS_UNDEFINED;
}

void clear()
static JSValue log(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
_console.Clear();
if (const auto console = gScConsole.GetOpaque<InteractiveConsole*>(thisVal))
{
std::string line;
for (int i = 0; i < argc; i++)
{
if (i != 0)
line.push_back(' ');
line += Stringify(ctx, argv[i]);
}
console->WriteLine(line);
}
return JS_UNDEFINED;
}

duk_ret_t log(duk_context* ctx)
static JSValue executeLegacy(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
std::string line;
auto nargs = duk_get_top(ctx);
for (duk_idx_t i = 0; i < nargs; i++)
if (const auto console = gScConsole.GetOpaque<InteractiveConsole*>(thisVal))
{
auto arg = DukValue::copy_from_stack(ctx, i);
auto argsz = Stringify(arg);
if (i != 0)
if (!JS_IsString(argv[0]))
{
line.push_back(' ');
JS_ThrowTypeError(ctx, "Argument 0: expected string");
return JS_EXCEPTION;
}
line += argsz;
auto str = JS_ToCString(ctx, argv[0]);
console->Execute(str);
JS_FreeCString(ctx, str);
}
_console.WriteLine(line);
return 0;
return JS_UNDEFINED;
}

static constexpr JSCFunctionListEntry funcs[] = {
JS_CFUNC_DEF("clear", 0, clear),
JS_CFUNC_DEF("log", 0, log),
JS_CFUNC_DEF("executeLegacy", 0, executeLegacy),
};

public:
const JSClassDef* GetClassDef() override
{
return &classDef;
}

void executeLegacy(const std::string& command)
std::span<const JSCFunctionListEntry> GetClassFuncs() override
{
_console.Execute(command);
return std::span<const JSCFunctionListEntry>{ funcs };
}

static void Register(duk_context* ctx)
JSValue New(JSContext* ctx, InteractiveConsole& console)
{
dukglue_register_method(ctx, &ScConsole::clear, "clear");
dukglue_register_method_varargs(ctx, &ScConsole::log, "log");
dukglue_register_method(ctx, &ScConsole::executeLegacy, "executeLegacy");
return MakeWithOpaque(ctx, &console);
}
};
} // namespace OpenRCT2::Scripting
Expand Down

0 comments on commit 4491680

Please sign in to comment.