mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-06-21 01:04:06 +00:00
Add a class to register stateful callbacks to JS
Use the class in autostart as an example.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2024 Wildfire Games.
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@@ -23,9 +23,11 @@
|
||||
#include "ScriptRequest.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
class ScriptInterface;
|
||||
@@ -257,6 +259,13 @@ private:
|
||||
return !ScriptException::CatchPending(rq) && success;
|
||||
}
|
||||
|
||||
struct StatefullCallbackPrivateSlot
|
||||
{
|
||||
static constexpr size_t callback{0};
|
||||
static constexpr size_t classInfo{1};
|
||||
static constexpr uint32_t count{2};
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
public:
|
||||
@@ -453,6 +462,92 @@ public:
|
||||
{
|
||||
JS_DefineFunction(cx, scope, name, &ToJSNative<callable, thisGetter>, args_info<decltype(callable)>::nb_args, flags);
|
||||
}
|
||||
|
||||
template<typename Callable>
|
||||
class StatefulCallback
|
||||
{
|
||||
class ClassInfo
|
||||
{
|
||||
public:
|
||||
ClassInfo(std::string className) :
|
||||
name{std::move(className)}
|
||||
{}
|
||||
|
||||
JSClassOps classOps{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
/*.finalize = */finalize, /*.call = */&ToJSNative<&Callable::operator(), getter>,
|
||||
nullptr, nullptr};
|
||||
std::string name;
|
||||
JSClass jsClass{name.c_str(),
|
||||
JSCLASS_HAS_RESERVED_SLOTS(StatefullCallbackPrivateSlot::count) |
|
||||
JSCLASS_BACKGROUND_FINALIZE,
|
||||
&classOps};
|
||||
};
|
||||
public:
|
||||
explicit StatefulCallback(const ScriptRequest& rq, std::string name, Callable callable) :
|
||||
StatefulCallback{rq, std::move(name),
|
||||
JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT, std::move(callable)}
|
||||
{}
|
||||
|
||||
explicit StatefulCallback(const ScriptRequest& rq, std::string name, const unsigned flags,
|
||||
Callable callable) :
|
||||
callback{std::move(callable)},
|
||||
functionObject{rq.cx}
|
||||
{
|
||||
auto classInfo = std::make_unique<ClassInfo>(std::move(name));
|
||||
functionObject.set(JS_NewObject(rq.cx, &classInfo->jsClass));
|
||||
JS::RootedValue functionValue{rq.cx, JS::ObjectValue(*functionObject)};
|
||||
if (!JS_DefineProperty(rq.cx, rq.nativeScope, classInfo->name.c_str(), functionValue,
|
||||
flags))
|
||||
{
|
||||
throw std::runtime_error{fmt::format(
|
||||
"Failed defining function {:?} on the native scope.", classInfo->name)};
|
||||
}
|
||||
JS::SetReservedSlot(functionObject, StatefullCallbackPrivateSlot::classInfo,
|
||||
JS::PrivateValue(classInfo.release()));
|
||||
JS::SetReservedSlot(functionObject, StatefullCallbackPrivateSlot::callback,
|
||||
JS::PrivateValue(&callback));
|
||||
}
|
||||
StatefulCallback(const StatefulCallback&) = delete;
|
||||
StatefulCallback& operator=(const StatefulCallback&) = delete;
|
||||
StatefulCallback(StatefulCallback&&) = delete;
|
||||
StatefulCallback& operator=(StatefulCallback&&) = delete;
|
||||
|
||||
~StatefulCallback()
|
||||
{
|
||||
JS::SetReservedSlot(functionObject, StatefullCallbackPrivateSlot::callback,
|
||||
JS::UndefinedValue());
|
||||
}
|
||||
|
||||
private:
|
||||
static Callable* getter(const ScriptRequest&, JS::CallArgs& args)
|
||||
{
|
||||
return JS::GetMaybePtrFromReservedSlot<Callable>(&args.callee(),
|
||||
StatefullCallbackPrivateSlot::callback);
|
||||
}
|
||||
|
||||
static void finalize(JS::GCContext*, JSObject* obj)
|
||||
{
|
||||
delete JS::GetMaybePtrFromReservedSlot<ClassInfo>(obj,
|
||||
StatefullCallbackPrivateSlot::classInfo);
|
||||
}
|
||||
|
||||
Callable callback;
|
||||
JS::RootedObject functionObject;
|
||||
};
|
||||
|
||||
template<typename Callable>
|
||||
static StatefulCallback<Callable> Register(const ScriptRequest& rq, std::string name,
|
||||
const unsigned flags, Callable callable)
|
||||
{
|
||||
return StatefulCallback{rq, std::move(name), flags, std::move(callable)};
|
||||
}
|
||||
|
||||
template<typename Callable>
|
||||
static StatefulCallback<Callable> Register(const ScriptRequest& rq, std::string name,
|
||||
Callable callable)
|
||||
{
|
||||
return StatefulCallback{rq, std::move(name), std::move(callable)};
|
||||
}
|
||||
};
|
||||
|
||||
#endif // INCLUDED_FUNCTIONWRAPPER
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2021 Wildfire Games.
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@@ -110,4 +110,24 @@ public:
|
||||
TS_ASSERT_EQUALS(ret, 4);
|
||||
}
|
||||
}
|
||||
|
||||
void test_statefull()
|
||||
{
|
||||
ScriptInterface script{"Test", "Test", g_ScriptContext};
|
||||
const ScriptRequest rq{script};
|
||||
JS::RootedValue nativeScope{rq.cx, JS::ObjectValue(*rq.nativeScope)};
|
||||
|
||||
constexpr const char* name{"callback"};
|
||||
{
|
||||
bool called{false};
|
||||
auto _ = ScriptFunction::Register(rq, name, [&](){
|
||||
called = true;
|
||||
});
|
||||
TS_ASSERT(!called);
|
||||
TS_ASSERT(ScriptFunction::CallVoid(rq, nativeScope, name));
|
||||
TS_ASSERT(called);
|
||||
}
|
||||
|
||||
TS_ASSERT(!ScriptFunction::CallVoid(rq, nativeScope, name));
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user