Ykkrosh
8e02ec84f9
Changed player-id code a bit so the entity and actor and unit should stay in sync more often. (The entity/actor/unit mixing still looks a bit dodgy and unreliable, though.) Simplified console help code. Allowed init/shutdown to be done with the simulation/world/etc parts disabled (so the actor viewer can load faster). This was SVN commit r4289.
457 lines
11 KiB
C++
457 lines
11 KiB
C++
#include "precompiled.h"
|
|
|
|
#include <sstream>
|
|
|
|
#include "ScriptingHost.h"
|
|
#include "ScriptGlue.h"
|
|
#include "ps/Profile.h"
|
|
#include "ps/CLogger.h"
|
|
|
|
#include "lib/res/res.h"
|
|
|
|
|
|
#if OS_WIN
|
|
#ifdef NDEBUG
|
|
# pragma comment (lib, "js32.lib")
|
|
#else
|
|
# pragma comment (lib, "js32d.lib")
|
|
#endif
|
|
#endif
|
|
|
|
#define LOG_CATEGORY "scriptinghost"
|
|
|
|
namespace
|
|
{
|
|
const int RUNTIME_MEMORY_ALLOWANCE = 16 * 1024 * 1024;
|
|
const int STACK_CHUNK_SIZE = 16 * 1024;
|
|
|
|
JSClass GlobalClass =
|
|
{
|
|
"global", 0,
|
|
JS_PropertyStub, JS_PropertyStub,
|
|
JS_PropertyStub, JS_PropertyStub,
|
|
JS_EnumerateStub, JS_ResolveStub,
|
|
JS_ConvertStub, JS_FinalizeStub
|
|
};
|
|
}
|
|
|
|
ScriptingHost::ScriptingHost()
|
|
: m_RunTime(NULL), m_Context(NULL), m_GlobalObject(NULL)
|
|
{
|
|
m_RunTime = JS_NewRuntime(RUNTIME_MEMORY_ALLOWANCE);
|
|
if(!m_RunTime)
|
|
throw PSERROR_Scripting_SetupFailed();
|
|
|
|
m_Context = JS_NewContext(m_RunTime, STACK_CHUNK_SIZE);
|
|
if(!m_Context)
|
|
throw PSERROR_Scripting_SetupFailed();
|
|
|
|
JS_SetErrorReporter(m_Context, ScriptingHost::ErrorReporter);
|
|
|
|
m_GlobalObject = JS_NewObject(m_Context, &GlobalClass, NULL, NULL);
|
|
if(!m_GlobalObject)
|
|
throw PSERROR_Scripting_SetupFailed();
|
|
|
|
#ifndef NDEBUG
|
|
// Register our script and function handlers - note: docs say they don't like
|
|
// nulls passed as closures, nor should they return nulls.
|
|
JS_SetExecuteHook( m_RunTime, jshook_script, this );
|
|
JS_SetCallHook( m_RunTime, jshook_function, this );
|
|
#endif
|
|
|
|
if (JS_InitStandardClasses(m_Context, m_GlobalObject) == JSVAL_FALSE)
|
|
throw PSERROR_Scripting_SetupFailed();
|
|
|
|
if (JS_DefineFunctions(m_Context, m_GlobalObject, ScriptFunctionTable) == JS_FALSE)
|
|
throw PSERROR_Scripting_SetupFailed();
|
|
|
|
if( JS_DefineProperties( m_Context, m_GlobalObject, ScriptGlobalTable ) == JS_FALSE )
|
|
throw PSERROR_Scripting_SetupFailed();
|
|
}
|
|
|
|
ScriptingHost::~ScriptingHost()
|
|
{
|
|
if (m_Context != NULL)
|
|
{
|
|
JS_DestroyContext(m_Context);
|
|
m_Context = NULL;
|
|
}
|
|
|
|
if (m_RunTime != NULL)
|
|
{
|
|
JS_DestroyRuntime(m_RunTime);
|
|
m_RunTime = NULL;
|
|
}
|
|
|
|
JS_ShutDown();
|
|
}
|
|
|
|
// filename, line and globalObject default to 0 (in which case we execute
|
|
// the whole script / use our m_GlobalObject)
|
|
void ScriptingHost::RunMemScript(const char* script, size_t size, const char* filename, int line, JSObject* globalObject)
|
|
{
|
|
if(!filename)
|
|
filename = "unspecified file";
|
|
if(!globalObject)
|
|
globalObject = m_GlobalObject;
|
|
|
|
// Maybe TODO: support Unicode input formats?
|
|
|
|
jsval rval;
|
|
JSBool ok = JS_EvaluateScript(m_Context, globalObject, script,
|
|
(uint)size, filename, line, &rval);
|
|
|
|
if (ok == JS_FALSE)
|
|
throw PSERROR_Scripting_LoadFile_EvalErrors();
|
|
}
|
|
|
|
// globalObject defaults to 0 (in which case we use our m_GlobalObject).
|
|
void ScriptingHost::RunScript(const CStr& filename, JSObject* globalObject)
|
|
{
|
|
const char* fn = filename.c_str();
|
|
|
|
FileIOBuf buf;
|
|
size_t size;
|
|
if(vfs_load(fn, buf, size) != INFO_OK) // ERRTODO: translate/pass it on
|
|
throw PSERROR_Scripting_LoadFile_OpenFailed();
|
|
|
|
const char* script = (const char*)buf;
|
|
RunMemScript(script, size, fn, 1, globalObject);
|
|
|
|
(void)file_buf_free(buf);
|
|
}
|
|
|
|
jsval ScriptingHost::CallFunction(const std::string & functionName, jsval * params, int numParams)
|
|
{
|
|
jsval result;
|
|
|
|
JSBool ok = JS_CallFunctionName(m_Context, m_GlobalObject, functionName.c_str(), numParams, params, &result);
|
|
|
|
if (ok == JS_FALSE)
|
|
throw PSERROR_Scripting_CallFunctionFailed();
|
|
|
|
return result;
|
|
}
|
|
|
|
jsval ScriptingHost::ExecuteScript(const CStrW& script, const CStrW& calledFrom, JSObject* contextObject )
|
|
{
|
|
jsval rval;
|
|
|
|
/* Unicode->ASCII conversion (mostly) for calledFrom */
|
|
|
|
size_t len = wcstombs( NULL, calledFrom, 0 );
|
|
debug_assert( len != (size_t)-1 );
|
|
char* asciiName = new char[len + 1];
|
|
wcstombs( asciiName, calledFrom, len + 1 );
|
|
|
|
JSBool ok = JS_EvaluateUCScript(m_Context, contextObject ? contextObject : m_GlobalObject, script.utf16().c_str(), (int)script.Length(), asciiName, 1, &rval);
|
|
|
|
delete[]( asciiName );
|
|
|
|
if (!ok) return JSVAL_NULL;
|
|
|
|
return rval;
|
|
}
|
|
|
|
// unused
|
|
void ScriptingHost::RegisterFunction(const std::string & functionName, JSNative function, int numArgs)
|
|
{
|
|
JSFunction * func = JS_DefineFunction(m_Context, m_GlobalObject, functionName.c_str(), function, numArgs, 0);
|
|
|
|
if (func == NULL)
|
|
throw PSERROR_Scripting_RegisterFunctionFailed();
|
|
}
|
|
|
|
void ScriptingHost::DefineConstant(const std::string & name, int value)
|
|
{
|
|
// First remove this constant if it already exists
|
|
JS_DeleteProperty(m_Context, m_GlobalObject, name.c_str());
|
|
|
|
JSBool ok = JS_DefineProperty( m_Context, m_GlobalObject, name.c_str(), INT_TO_JSVAL(value),
|
|
NULL, NULL, JSPROP_READONLY);
|
|
|
|
if (ok == JS_FALSE)
|
|
throw PSERROR_Scripting_DefineConstantFailed();
|
|
}
|
|
|
|
void ScriptingHost::DefineConstant(const std::string & name, double value)
|
|
{
|
|
// First remove this constant if it already exists
|
|
JS_DeleteProperty(m_Context, m_GlobalObject, name.c_str());
|
|
|
|
struct JSConstDoubleSpec spec[2];
|
|
|
|
spec[0].name = name.c_str();
|
|
spec[0].dval = value;
|
|
spec[0].flags = JSPROP_READONLY;
|
|
|
|
spec[1].name = 0;
|
|
spec[1].dval = 0.0;
|
|
spec[1].flags = 0;
|
|
|
|
JSBool ok = JS_DefineConstDoubles(m_Context, m_GlobalObject, spec);
|
|
|
|
if (ok == JS_FALSE)
|
|
throw PSERROR_Scripting_DefineConstantFailed();
|
|
}
|
|
|
|
void ScriptingHost::DefineCustomObjectType(JSClass *clasp, JSNative constructor, uintN minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs)
|
|
{
|
|
std::string typeName = clasp->name;
|
|
|
|
if (m_CustomObjectTypes.find(typeName) != m_CustomObjectTypes.end())
|
|
{
|
|
// This type already exists
|
|
throw PSERROR_Scripting_DefineType_AlreadyExists();
|
|
}
|
|
|
|
JSObject * obj = JS_InitClass( m_Context, m_GlobalObject, 0,
|
|
clasp,
|
|
constructor, minArgs, // Constructor, min args
|
|
ps, fs, // Properties, methods
|
|
static_ps, static_fs); // Constructor properties, methods
|
|
|
|
if (obj != NULL)
|
|
{
|
|
CustomType type;
|
|
|
|
type.m_Object = obj;
|
|
type.m_Class = clasp;
|
|
|
|
m_CustomObjectTypes[typeName] = type;
|
|
}
|
|
else
|
|
{
|
|
throw PSERROR_Scripting_DefineType_CreationFailed();
|
|
}
|
|
}
|
|
|
|
JSObject * ScriptingHost::CreateCustomObject(const std::string & typeName)
|
|
{
|
|
std::map < std::string, CustomType > ::iterator it = m_CustomObjectTypes.find(typeName);
|
|
|
|
if (it == m_CustomObjectTypes.end())
|
|
throw PSERROR_Scripting_TypeDoesNotExist();
|
|
|
|
return JS_ConstructObject(m_Context, (*it).second.m_Class, (*it).second.m_Object, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ScriptingHost::SetObjectProperty(JSObject * object, const std::string & propertyName, jsval value)
|
|
{
|
|
JS_SetProperty(m_Context, object, propertyName.c_str(), &value);
|
|
}
|
|
|
|
jsval ScriptingHost::GetObjectProperty( JSObject* object, const std::string& propertyName )
|
|
{
|
|
jsval vp;
|
|
JS_GetProperty( m_Context, object, propertyName.c_str(), &vp );
|
|
return( vp );
|
|
}
|
|
|
|
|
|
|
|
void ScriptingHost::SetObjectProperty_Double(JSObject* object, const char* propertyName, double value)
|
|
{
|
|
jsdouble* d = JS_NewDouble(m_Context, value);
|
|
if (! d)
|
|
throw PSERROR_Scripting_ConversionFailed();
|
|
|
|
jsval v = DOUBLE_TO_JSVAL(d);
|
|
|
|
if (! JS_SetProperty(m_Context, object, propertyName, &v))
|
|
throw PSERROR_Scripting_ConversionFailed();
|
|
}
|
|
|
|
double ScriptingHost::GetObjectProperty_Double(JSObject* object, const char* propertyName)
|
|
{
|
|
jsval v;
|
|
double d;
|
|
|
|
if (! JS_GetProperty(m_Context, object, propertyName, &v))
|
|
throw PSERROR_Scripting_ConversionFailed();
|
|
if (! JS_ValueToNumber(m_Context, v, &d))
|
|
throw PSERROR_Scripting_ConversionFailed();
|
|
return d;
|
|
}
|
|
|
|
|
|
|
|
void ScriptingHost::SetGlobal(const std::string &globalName, jsval value)
|
|
{
|
|
JS_SetProperty(m_Context, m_GlobalObject, globalName.c_str(), &value);
|
|
}
|
|
|
|
// unused
|
|
jsval ScriptingHost::GetGlobal(const std::string &globalName)
|
|
{
|
|
jsval vp;
|
|
JS_GetProperty(m_Context, m_GlobalObject, globalName.c_str(), &vp);
|
|
return vp;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// conversions
|
|
//----------------------------------------------------------------------------
|
|
/*
|
|
// These have been removed in favour of ToPrimitive<int>(value)
|
|
|
|
int ScriptingHost::ValueToInt(const jsval value)
|
|
{
|
|
int32 i = 0;
|
|
|
|
JSBool ok = JS_ValueToInt32(m_Context, value, &i);
|
|
|
|
if (!ok)
|
|
throw PSERROR_Scripting_ConversionFailed();
|
|
|
|
return i;
|
|
}
|
|
|
|
bool ScriptingHost::ValueToBool(const jsval value)
|
|
{
|
|
JSBool b;
|
|
|
|
JSBool ok = JS_ValueToBoolean(m_Context, value, &b);
|
|
|
|
if (!ok)
|
|
throw PSERROR_Scripting_ConversionFailed();
|
|
|
|
return b == JS_TRUE;
|
|
}
|
|
|
|
|
|
double ScriptingHost::ValueToDouble(const jsval value)
|
|
{
|
|
jsdouble d;
|
|
|
|
JSBool ok = JS_ValueToNumber(m_Context, value, &d);
|
|
|
|
if (ok == JS_FALSE || !isfinite(d))
|
|
throw PSERROR_Scripting_ConversionFailed();
|
|
|
|
return d;
|
|
}
|
|
|
|
|
|
*/
|
|
std::string ScriptingHost::ValueToString(const jsval value)
|
|
{
|
|
JSString* string = JS_ValueToString(m_Context, value);
|
|
if (string == NULL)
|
|
throw PSERROR_Scripting_ConversionFailed();
|
|
|
|
return std::string(JS_GetStringBytes(string), JS_GetStringLength(string));
|
|
}
|
|
|
|
CStrW ScriptingHost::ValueToUCString( const jsval value )
|
|
{
|
|
return CStrW(ValueToUTF16(value));
|
|
}
|
|
|
|
jsval ScriptingHost::UCStringToValue( const CStrW& str )
|
|
{
|
|
utf16string utf16=str.utf16();
|
|
return UTF16ToValue(utf16);
|
|
}
|
|
|
|
|
|
|
|
utf16string ScriptingHost::ValueToUTF16( const jsval value )
|
|
{
|
|
JSString* string = JS_ValueToString(m_Context, value);
|
|
if (string == NULL)
|
|
throw PSERROR_Scripting_ConversionFailed();
|
|
|
|
jschar *strptr=JS_GetStringChars(string);
|
|
size_t length=JS_GetStringLength(string);
|
|
return utf16string(strptr, strptr+length);
|
|
}
|
|
|
|
jsval ScriptingHost::UTF16ToValue(const utf16string &str)
|
|
{
|
|
return STRING_TO_JSVAL(JS_NewUCStringCopyZ(m_Context, str.c_str()));
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// called by SpiderMonkey whenever someone does JS_ReportError.
|
|
// prints that message as well as locus to log, debug output and console.
|
|
void ScriptingHost::ErrorReporter(JSContext* UNUSED(cx), const char* message, JSErrorReport* report)
|
|
{
|
|
const char* file = report->filename;
|
|
const int line = report->lineno;
|
|
// apparently there is no further information in this struct we can use
|
|
// because linebuf/tokenptr require a buffer to have been allocated.
|
|
// that doesn't look possible since we are a callback and there is
|
|
// no mention in the dox about where this would happen (typical).
|
|
|
|
if(!file)
|
|
file = "(current document)";
|
|
if(!message)
|
|
message = "No error message available";
|
|
|
|
// for developer convenience: write to output window so they can
|
|
// doubleclick on that line and be taken to the error locus.
|
|
debug_printf("%s(%d): %s\n", file, line, message);
|
|
|
|
// note: CLogger's LOG already takes care of writing to the console,
|
|
// so don't do that here.
|
|
|
|
LOG(ERROR, LOG_CATEGORY, "JavaScript Error (%s, line %d): %s", file, line, message);
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
|
|
void* ScriptingHost::jshook_script( JSContext* UNUSED(cx), JSStackFrame* UNUSED(fp),
|
|
JSBool before, JSBool* UNUSED(ok), void* closure )
|
|
{
|
|
if( before )
|
|
{
|
|
g_Profiler.StartScript( "script invocation" );
|
|
}
|
|
else
|
|
g_Profiler.Stop();
|
|
|
|
return( closure );
|
|
}
|
|
|
|
void* ScriptingHost::jshook_function( JSContext* cx, JSStackFrame* fp, JSBool before, JSBool* UNUSED(ok), void* closure )
|
|
{
|
|
JSFunction* fn = JS_GetFrameFunction( cx, fp );
|
|
if( before )
|
|
{
|
|
if( fn )
|
|
{
|
|
g_Profiler.StartScript( JS_GetFunctionName( fn ) );
|
|
}
|
|
else
|
|
g_Profiler.StartScript( "function invokation" );
|
|
}
|
|
else
|
|
g_Profiler.Stop();
|
|
|
|
return( closure );
|
|
}
|
|
|
|
#endif
|