mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-06-22 21:27:36 +00:00
# Added scripting support to the scenario editor UI
Added modified version of wxJavaScript. Added Atlas script interface. Rewrote the Map sidebar in JS. This was SVN commit r5144.
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
#include "ScriptInterface.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "js/jsapi.h"
|
||||
|
||||
#include "wx/wx.h"
|
||||
|
||||
#include "wxJS/common/main.h"
|
||||
#include "wxJS/ext/jsmembuf.h"
|
||||
#include "wxJS/io/init.h"
|
||||
#include "wxJS/gui/init.h"
|
||||
#include "wxJS/gui/control/window.h"
|
||||
|
||||
#include "GameInterface/Shareable.h"
|
||||
#include "GameInterface/Messages.h"
|
||||
|
||||
// We want to include Messages.h again below, with some different definitions,
|
||||
// so cheat and undefine its include-guard
|
||||
#undef INCLUDED_MESSAGES
|
||||
|
||||
#include <boost/preprocessor/punctuation/comma_if.hpp>
|
||||
#include <boost/preprocessor/repetition/repeat.hpp>
|
||||
|
||||
const int RUNTIME_SIZE = 1024*1024; // TODO: how much memory is needed?
|
||||
const int STACK_CHUNK_SIZE = 8192;
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template<typename T> bool ScriptInterface::FromJSVal(JSContext* cx, jsval WXUNUSED(v), T& WXUNUSED(out))
|
||||
{
|
||||
JS_ReportError(cx, "Unrecognised argument type");
|
||||
// TODO: SetPendingException turns the error into a JS-catchable exception,
|
||||
// but the error report doesn't say anything useful like the line number,
|
||||
// so I'm just using ReportError instead for now (and failures are uncatchable
|
||||
// and will terminate the whole script)
|
||||
//JS_SetPendingException(cx, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, "Unrecognised argument type")));
|
||||
return false;
|
||||
}
|
||||
|
||||
template<> bool ScriptInterface::FromJSVal<bool>(JSContext* cx, jsval v, bool& out)
|
||||
{
|
||||
JSBool ret;
|
||||
if (! JS_ValueToBoolean(cx, v, &ret)) return false;
|
||||
out = ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<> bool ScriptInterface::FromJSVal<float>(JSContext* cx, jsval v, float& out)
|
||||
{
|
||||
jsdouble ret;
|
||||
if (! JS_ValueToNumber(cx, v, &ret)) return false;
|
||||
out = ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<> bool ScriptInterface::FromJSVal<int>(JSContext* cx, jsval v, int& out)
|
||||
{
|
||||
int32 ret;
|
||||
if (! JS_ValueToInt32(cx, v, &ret)) return false;
|
||||
out = ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<> bool ScriptInterface::FromJSVal<std::wstring>(JSContext* cx, jsval v, std::wstring& out)
|
||||
{
|
||||
JSString* ret = JS_ValueToString(cx, v); // never returns NULL
|
||||
jschar* ch = JS_GetStringChars(ret);
|
||||
out = std::wstring(ch, ch+JS_GetStringLength(ret));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
template<> jsval ScriptInterface::ToJSVal<float>(JSContext* cx, const float& val)
|
||||
{
|
||||
jsval rval = JSVAL_VOID;
|
||||
JS_NewDoubleValue(cx, val, &rval); // ignore return value
|
||||
return rval;
|
||||
}
|
||||
|
||||
template<> jsval ScriptInterface::ToJSVal<int>(JSContext* WXUNUSED(cx), const int& val)
|
||||
{
|
||||
return INT_TO_JSVAL(val);
|
||||
}
|
||||
|
||||
template<> jsval ScriptInterface::ToJSVal<wxString>(JSContext* cx, const wxString& val)
|
||||
{
|
||||
wxMBConvUTF16 conv;
|
||||
size_t length;
|
||||
wxCharBuffer utf16 = conv.cWC2MB(val.c_str(), val.length()+1, &length);
|
||||
JSString* str = JS_NewUCStringCopyN(cx, reinterpret_cast<jschar*>(utf16.data()), length/2);
|
||||
if (str)
|
||||
return STRING_TO_JSVAL(str);
|
||||
else
|
||||
return JSVAL_VOID;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
struct ScriptInterface_impl
|
||||
{
|
||||
ScriptInterface_impl();
|
||||
~ScriptInterface_impl();
|
||||
void LoadScript(const wxString& filename, const wxString& code);
|
||||
void RegisterMessages(JSObject* parent);
|
||||
void Register(const char* name, JSNative fptr, uintN nargs);
|
||||
|
||||
JSRuntime* m_rt;
|
||||
JSContext* m_cx;
|
||||
JSObject* m_glob; // global scope object
|
||||
JSObject* m_atlas; // Atlas scope object
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
JSClass global_class = {
|
||||
"global", 0,
|
||||
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
|
||||
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub
|
||||
};
|
||||
|
||||
void ErrorReporter(JSContext* WXUNUSED(cx), const char* message, JSErrorReport* report)
|
||||
{
|
||||
bool isWarning = JSREPORT_IS_WARNING(report->flags);
|
||||
wxString logMessage(isWarning ? _T("JavaScript warning: ") : _T("JavaScript error: "));
|
||||
if (report->filename)
|
||||
{
|
||||
logMessage << wxString::FromAscii(report->filename);
|
||||
logMessage << _T(" line ") << report->lineno << _T("\n");
|
||||
}
|
||||
logMessage << wxString::FromAscii(message);
|
||||
if (isWarning)
|
||||
wxLogWarning(logMessage);
|
||||
else
|
||||
wxLogError(logMessage);
|
||||
}
|
||||
|
||||
JSBool LoadScript(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval)
|
||||
{
|
||||
if (! ( JSVAL_IS_STRING(argv[0]) && JSVAL_IS_STRING(argv[1]) ))
|
||||
return JS_FALSE;
|
||||
|
||||
JSString* name = JSVAL_TO_STRING(argv[0]);
|
||||
JSString* code = JSVAL_TO_STRING(argv[1]);
|
||||
|
||||
JSObject* scriptObj = JS_NewObject(cx, NULL, NULL, NULL);
|
||||
if (! scriptObj)
|
||||
return JS_FALSE;
|
||||
*rval = OBJECT_TO_JSVAL(scriptObj); // root it to keep GC happy
|
||||
|
||||
jsval scriptRval;
|
||||
JSBool ok = JS_EvaluateUCScript(
|
||||
cx, scriptObj, JS_GetStringChars(code), JS_GetStringLength(code),
|
||||
JS_GetStringBytes(name), 1, &scriptRval);
|
||||
if (! ok)
|
||||
return JS_FALSE;
|
||||
|
||||
return JS_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
ScriptInterface_impl::ScriptInterface_impl()
|
||||
{
|
||||
JSBool ok;
|
||||
|
||||
m_rt = JS_NewRuntime(RUNTIME_SIZE);
|
||||
assert(m_rt); // TODO: error handling
|
||||
|
||||
m_cx = JS_NewContext(m_rt, STACK_CHUNK_SIZE);
|
||||
assert(m_cx);
|
||||
|
||||
JS_BeginRequest(m_cx); // if you get linker errors, see the comment in the .h about JS_THREADSAFE
|
||||
|
||||
JS_SetErrorReporter(m_cx, ErrorReporter);
|
||||
|
||||
JS_SetOptions(m_cx,
|
||||
JSOPTION_STRICT // "warn on dubious practice"
|
||||
| JSOPTION_XML // "ECMAScript for XML support: parse <!-- --> as a token"
|
||||
);
|
||||
|
||||
m_glob = JS_NewObject(m_cx, &global_class, NULL, NULL);
|
||||
ok = JS_InitStandardClasses(m_cx, m_glob);
|
||||
|
||||
wxjs::gui::InitClass(m_cx, m_glob);
|
||||
wxjs::io::InitClass(m_cx, m_glob);
|
||||
wxjs::ext::MemoryBuffer::JSInit(m_cx, m_glob);
|
||||
|
||||
m_atlas = JS_DefineObject(m_cx, m_glob, "Atlas", NULL, NULL, JSPROP_READONLY|JSPROP_PERMANENT);
|
||||
JS_DefineFunction(m_cx, m_atlas, "LoadScript", ::LoadScript, 2, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
|
||||
|
||||
RegisterMessages(m_atlas);
|
||||
}
|
||||
|
||||
ScriptInterface_impl::~ScriptInterface_impl()
|
||||
{
|
||||
JS_EndRequest(m_cx);
|
||||
JS_DestroyContext(m_cx);
|
||||
JS_DestroyRuntime(m_rt);
|
||||
}
|
||||
|
||||
void ScriptInterface_impl::LoadScript(const wxString& filename, const wxString& code)
|
||||
{
|
||||
size_t codeLength;
|
||||
wxMBConvUTF16 conv;
|
||||
wxCharBuffer codeUTF16 = conv.cWC2MB(code.c_str(), code.length()+1, &codeLength);
|
||||
jsval rval;
|
||||
JS_EvaluateUCScript(m_cx, m_glob, reinterpret_cast<jschar*>(codeUTF16.data()), codeLength/2, filename.ToAscii(), 1, &rval);
|
||||
}
|
||||
|
||||
void ScriptInterface_impl::Register(const char* name, JSNative fptr, uintN nargs)
|
||||
{
|
||||
JS_DefineFunction(m_cx, m_atlas, name, fptr, nargs, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
|
||||
}
|
||||
|
||||
|
||||
ScriptInterface::ScriptInterface()
|
||||
: m(new ScriptInterface_impl)
|
||||
{
|
||||
}
|
||||
|
||||
ScriptInterface::~ScriptInterface()
|
||||
{
|
||||
}
|
||||
|
||||
void ScriptInterface::Register(const char* name, JSNative fptr, size_t nargs)
|
||||
{
|
||||
m->Register(name, fptr, nargs);
|
||||
}
|
||||
|
||||
// wxJS requires an object that's a wxWindow* and also a wxjs::Object*,
|
||||
// so define one based on wxPanel
|
||||
struct WindowWrapper : public wxPanel, wxjs::Object
|
||||
{
|
||||
WindowWrapper(wxWindow* parent)
|
||||
: wxPanel(parent, -1)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void ScriptInterface::LoadScript(const wxString& filename, const wxString& code)
|
||||
{
|
||||
m->LoadScript(filename, code);
|
||||
}
|
||||
|
||||
wxPanel* ScriptInterface::LoadScriptAsPanel(const wxString& name, wxWindow* parent)
|
||||
{
|
||||
wxPanel* panel = new WindowWrapper(parent);
|
||||
jsval jsWindow = wxjs::gui::Window::CreateObject(m->m_cx, panel);
|
||||
jsval jsName = ToJSVal(m->m_cx, name);
|
||||
|
||||
const uintN argc = 2;
|
||||
jsval argv[argc] = { jsName, jsWindow };
|
||||
|
||||
jsval rval;
|
||||
JS_CallFunctionName(m->m_cx, m->m_glob, "loadScript", argc, argv, &rval); // TODO: error checking
|
||||
return panel;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
struct MessageWrapper
|
||||
{
|
||||
#define NUMBERED_LIST(z, i, data) , data##i
|
||||
#define NUMBERED_LIST2(z, i, data) BOOST_PP_COMMA_IF(i) data##i
|
||||
#define CONVERT_ARGS(z, i, data) T##i a##i; if (! ScriptInterface::FromJSVal<T##i>(cx, argv[i], a##i)) return JS_FALSE;
|
||||
#define OVERLOADS(z, i, data) \
|
||||
template <typename Message BOOST_PP_REPEAT_##z (i, NUMBERED_LIST, typename T) > \
|
||||
static JSNative call(Message* (*WXUNUSED(fptr)) ( BOOST_PP_REPEAT_##z(i, NUMBERED_LIST2, T) )) { \
|
||||
return &_call<Message BOOST_PP_REPEAT_##z(i, NUMBERED_LIST, T) >; \
|
||||
} \
|
||||
template <typename Message BOOST_PP_REPEAT_##z (i, NUMBERED_LIST, typename T) > \
|
||||
static uintN nargs(Message* (*WXUNUSED(fptr)) ( BOOST_PP_REPEAT_##z(i, NUMBERED_LIST2, T) )) { \
|
||||
return i; \
|
||||
} \
|
||||
template <typename Message BOOST_PP_REPEAT_##z (i, NUMBERED_LIST, typename T) > \
|
||||
static JSBool _call(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* WXUNUSED(rval)) \
|
||||
{ \
|
||||
(void)cx; (void)obj; (void)argc; (void)argv; /* avoid 'unused parameter' warnings */ \
|
||||
BOOST_PP_REPEAT_##z (i, CONVERT_ARGS, ~) \
|
||||
AtlasMessage::g_MessagePasser->Add(SHAREABLE_NEW(Message, ( BOOST_PP_REPEAT_##z(i, NUMBERED_LIST2, a) ))); \
|
||||
return JS_TRUE; \
|
||||
}
|
||||
BOOST_PP_REPEAT(7, OVERLOADS, ~)
|
||||
};
|
||||
|
||||
void ScriptInterface_impl::RegisterMessages(JSObject* parent)
|
||||
{
|
||||
using namespace AtlasMessage;
|
||||
|
||||
JSFunction* ret;
|
||||
JSObject* obj = JS_DefineObject(m_cx, parent, "Message", NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
|
||||
|
||||
#define MESSAGE(name, vals) \
|
||||
ret = JS_DefineFunction(m_cx, obj, #name, MessageWrapper::call(&m##name::CtorType), MessageWrapper::nargs(&m##name::CtorType), JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT);
|
||||
|
||||
#define QUERY(name, in_vals, out_vals) /* TODO \
|
||||
extern void f##name##_wrapper(AtlasMessage::IMessage*); \
|
||||
AtlasMessage::GetMsgHandlers().insert(std::pair<std::string, AtlasMessage::msgHandler>(#name, &f##name##_wrapper));*/
|
||||
|
||||
#define COMMAND(name, merge, vals) /* TODO \
|
||||
extern cmdHandler c##name##_create(); \
|
||||
GetCmdHandlers().insert(std::pair<std::string, cmdHandler>("c"#name, c##name##_create()));*/
|
||||
|
||||
#undef SHAREABLE_STRUCT
|
||||
#define SHAREABLE_STRUCT(name)
|
||||
|
||||
#define MESSAGES_SKIP_SETUP
|
||||
#include "GameInterface/Messages.h"
|
||||
}
|
||||
Reference in New Issue
Block a user