Adding scripting interface code
This was SVN commit r281.
This commit is contained in:
parent
c427da4cd2
commit
8de401a3ee
@ -2,6 +2,10 @@
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
// Alan: For some reason if this gets included after anything else some
|
||||
// compile time errors get thrown up todo with javascript internal typedefs
|
||||
#include "scripting/ScriptingHost.h"
|
||||
|
||||
#include "sdl.h"
|
||||
#include "ogl.h"
|
||||
#include "detect.h"
|
||||
@ -25,6 +29,7 @@
|
||||
#include "EntityHandles.h"
|
||||
#include "EntityManager.h"
|
||||
|
||||
|
||||
#ifndef NO_GUI
|
||||
#include "gui/GUI.h"
|
||||
#endif
|
||||
@ -433,6 +438,8 @@ int main(int argc, char* argv[])
|
||||
ParseArgs(argc, argv);
|
||||
|
||||
|
||||
// Create the scripting host. This needs to be done before the GUI is created.
|
||||
new ScriptingHost;
|
||||
|
||||
// GUI is notified in set_vmode, so this must come before that.
|
||||
#ifndef NO_GUI
|
||||
@ -597,6 +604,7 @@ if(!g_MapFile)
|
||||
delete CGUI::GetSingletonPtr(); // again, we should have all singleton deletes somewhere
|
||||
#endif
|
||||
|
||||
delete &g_ScriptingHost;
|
||||
delete &g_Config;
|
||||
delete &g_EntityManager;
|
||||
delete &g_EntityTemplateCollection;
|
||||
|
38
source/scripting/ScriptCustomTypes.cpp
Executable file
38
source/scripting/ScriptCustomTypes.cpp
Executable file
@ -0,0 +1,38 @@
|
||||
|
||||
#include "ScriptingHost.h"
|
||||
|
||||
// POINT2D
|
||||
|
||||
JSClass Point2dClass =
|
||||
{
|
||||
"Point2d", 0,
|
||||
JS_PropertyStub, JS_PropertyStub,
|
||||
JS_PropertyStub, JS_PropertyStub,
|
||||
JS_EnumerateStub, JS_ResolveStub,
|
||||
JS_ConvertStub, JS_FinalizeStub
|
||||
};
|
||||
|
||||
JSPropertySpec Point2dProperties[] =
|
||||
{
|
||||
{"x", 0, JSPROP_ENUMERATE},
|
||||
{"y", 1, JSPROP_ENUMERATE},
|
||||
{0}
|
||||
};
|
||||
|
||||
JSBool Point2d_Constructor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
||||
{
|
||||
if (argc == 2)
|
||||
{
|
||||
g_ScriptingHost.SetObjectProperty(obj, "x", argv[0]);
|
||||
g_ScriptingHost.SetObjectProperty(obj, "y", argv[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
jsval zero = INT_TO_JSVAL(0);
|
||||
g_ScriptingHost.SetObjectProperty(obj, "x", zero);
|
||||
g_ScriptingHost.SetObjectProperty(obj, "y", zero);
|
||||
}
|
||||
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
17
source/scripting/ScriptCustomTypes.h
Executable file
17
source/scripting/ScriptCustomTypes.h
Executable file
@ -0,0 +1,17 @@
|
||||
|
||||
#ifndef _SCRIPTCUSTOMTYPES_H_
|
||||
#define _SCRIPTCUSTOMTYPES_H_
|
||||
|
||||
// Custom object types
|
||||
|
||||
// Whilst Point2d is fully coded, it is never registered so is not available in script
|
||||
// This is mostly as a demonstration of what you need to code to add a new type
|
||||
|
||||
// VECTOR2D
|
||||
extern JSClass Point2dClass;
|
||||
extern JSPropertySpec Point2dProperties[];
|
||||
JSBool Point2d_Constructor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
||||
|
||||
|
||||
|
||||
#endif
|
48
source/scripting/ScriptGlue.cpp
Executable file
48
source/scripting/ScriptGlue.cpp
Executable file
@ -0,0 +1,48 @@
|
||||
|
||||
#include "ScriptGlue.h"
|
||||
|
||||
// Parameters for the table are:
|
||||
|
||||
// 1: The name the function will be called as from script
|
||||
// 2: The number of aguments this function expects
|
||||
// 3: Depreciated, always zero
|
||||
// 4: Reserved for future use, always zero
|
||||
|
||||
JSFunctionSpec ScriptFunctionTable[] =
|
||||
{
|
||||
{"WriteLog", WriteLog, 1, 0, 0},
|
||||
|
||||
{0, 0, 0, 0, 0},
|
||||
};
|
||||
|
||||
// Allow scripts to output to the global log file
|
||||
JSBool WriteLog(JSContext * context, JSObject * globalObject, unsigned int argc, jsval * argv, jsval * rval)
|
||||
{
|
||||
if (argc < 1)
|
||||
return JS_FALSE;
|
||||
|
||||
for (int i = 0; i < (int)argc; i++)
|
||||
{
|
||||
if (JSVAL_IS_INT(argv[i]))
|
||||
{
|
||||
printf("%d", JSVAL_TO_INT(argv[i]));
|
||||
}
|
||||
|
||||
if (JSVAL_IS_DOUBLE(argv[i]))
|
||||
{
|
||||
double d = g_ScriptingHost.ValueToDouble(argv[i]);
|
||||
printf("%e", d);
|
||||
}
|
||||
|
||||
if (JSVAL_IS_STRING(argv[i]))
|
||||
{
|
||||
JSString * str = JS_ValueToString(context, argv[i]);
|
||||
char * chars = JS_GetStringBytes(str);
|
||||
printf(chars);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
return JS_TRUE;
|
||||
}
|
11
source/scripting/ScriptGlue.h
Executable file
11
source/scripting/ScriptGlue.h
Executable file
@ -0,0 +1,11 @@
|
||||
|
||||
#ifndef _SCRIPTGLUE_H_
|
||||
#define _SCRIPTGLUE_H_
|
||||
|
||||
#include "ScriptingHost.h"
|
||||
|
||||
JSBool WriteLog(JSContext * context, JSObject * globalObject, unsigned int argc, jsval *argv, jsval *rval);
|
||||
|
||||
JSFunctionSpec ScriptFunctionTable[];
|
||||
|
||||
#endif
|
319
source/scripting/ScriptingHost.cpp
Executable file
319
source/scripting/ScriptingHost.cpp
Executable file
@ -0,0 +1,319 @@
|
||||
|
||||
#include "ScriptingHost.h"
|
||||
#include "ScriptGlue.h"
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#pragma comment (lib, "js32.lib")
|
||||
|
||||
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 == NULL)
|
||||
{
|
||||
throw (std::string("ScriptingHost: Failed to create JavaScript runtime"));
|
||||
}
|
||||
|
||||
m_Context = JS_NewContext(m_RunTime, STACK_CHUNK_SIZE);
|
||||
|
||||
if (m_Context == NULL)
|
||||
{
|
||||
throw (std::string("ScriptingHost: Failed to create JavaScript context"));
|
||||
}
|
||||
|
||||
JS_SetErrorReporter(m_Context, ScriptingHost::ErrorReporter);
|
||||
|
||||
m_GlobalObject = JS_NewObject(m_Context, &GlobalClass, NULL, NULL);
|
||||
|
||||
if (m_GlobalObject == NULL)
|
||||
{
|
||||
throw (std::string("ScriptingHost: Failed to create global object"));
|
||||
}
|
||||
|
||||
if (JS_InitStandardClasses(m_Context, m_GlobalObject) == JSVAL_FALSE)
|
||||
{
|
||||
throw (std::string("ScriptingHost: Failed to init standard classes"));
|
||||
}
|
||||
|
||||
if (JS_DefineFunctions(m_Context, m_GlobalObject, ScriptFunctionTable) == JS_FALSE)
|
||||
{
|
||||
throw (std::string("ScriptingHost: Failed to setup native functions"));
|
||||
}
|
||||
|
||||
std::cout << "Scripting environment initialized" << std::endl;
|
||||
}
|
||||
|
||||
ScriptingHost::~ScriptingHost()
|
||||
{
|
||||
if (m_Context != NULL)
|
||||
{
|
||||
JS_DestroyContext(m_Context);
|
||||
m_Context = NULL;
|
||||
}
|
||||
|
||||
if (m_RunTime != NULL)
|
||||
{
|
||||
JS_DestroyRuntime(m_RunTime);
|
||||
m_RunTime = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptingHost::LoadScriptFromDisk(const std::string & fileName)
|
||||
{
|
||||
std::string script;
|
||||
std::string line;
|
||||
|
||||
std::ifstream scriptFile(fileName.c_str());
|
||||
|
||||
if (scriptFile.is_open() == false)
|
||||
{
|
||||
throw (std::string("Could not open file: ") + fileName);
|
||||
}
|
||||
|
||||
while (scriptFile.eof() == false)
|
||||
{
|
||||
std::getline(scriptFile, line);
|
||||
script += line;
|
||||
script += '\n';
|
||||
}
|
||||
|
||||
jsval rval;
|
||||
JSBool ok = JS_EvaluateScript(m_Context, m_GlobalObject, script.c_str(), (unsigned int)script.length(), fileName.c_str(), 0, &rval);
|
||||
|
||||
if (ok == JS_FALSE)
|
||||
{
|
||||
throw (std::string("Error loading script from disk: ") + fileName);
|
||||
}
|
||||
}
|
||||
|
||||
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 (std::string("Failure whilst calling script funtion: ") + functionName);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
jsval ScriptingHost::ExecuteScript(const std::string & script)
|
||||
{
|
||||
jsval rval;
|
||||
|
||||
JSBool ok = JS_EvaluateScript(m_Context, m_GlobalObject, script.c_str(), (int)script.length(), "Console", 0, &rval);
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
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 (std::string("Could not register function ") + functionName);
|
||||
}
|
||||
}
|
||||
|
||||
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 (std::string("Could not create constant"));
|
||||
}
|
||||
}
|
||||
|
||||
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 (std::string("Could not create constant"));
|
||||
}
|
||||
}
|
||||
|
||||
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 std::string("Type already exists");
|
||||
}
|
||||
|
||||
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 std::string("Type creation failed");
|
||||
}
|
||||
}
|
||||
|
||||
JSObject * ScriptingHost::CreateCustomObject(const std::string & typeName)
|
||||
{
|
||||
std::map < std::string, CustomType > ::iterator it = m_CustomObjectTypes.find(typeName);
|
||||
|
||||
if (it == m_CustomObjectTypes.end())
|
||||
{
|
||||
throw std::string("Tried to create a type that doesn't exist");
|
||||
}
|
||||
|
||||
return JS_NewObject(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);
|
||||
}
|
||||
|
||||
int ScriptingHost::ValueToInt(const jsval value)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
JSBool ok = JS_ValueToInt32(m_Context, value, (int32*)&i);
|
||||
|
||||
if (ok == JS_FALSE)
|
||||
{
|
||||
throw (std::string("Convert to int failed"));
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
bool ScriptingHost::ValueToBool(const jsval value)
|
||||
{
|
||||
JSBool b;
|
||||
|
||||
JSBool ok = JS_ValueToBoolean(m_Context, value, &b);
|
||||
|
||||
if (ok == JS_FALSE)
|
||||
{
|
||||
throw (std::string("Convert to bool failed"));
|
||||
}
|
||||
|
||||
return b == JS_TRUE;
|
||||
}
|
||||
|
||||
std::string ScriptingHost::ValueToString(const jsval value)
|
||||
{
|
||||
JSString * string = JS_ValueToString(m_Context, value);
|
||||
|
||||
return std::string(JS_GetStringBytes(string));
|
||||
}
|
||||
|
||||
double ScriptingHost::ValueToDouble(const jsval value)
|
||||
{
|
||||
jsdouble d;
|
||||
|
||||
JSBool ok = JS_ValueToNumber(m_Context, value, &d);
|
||||
|
||||
if (ok == JS_FALSE)
|
||||
{
|
||||
throw (std::string("Convert to double failed"));
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void ScriptingHost::ErrorReporter(JSContext * context, const char * message, JSErrorReport * report)
|
||||
{
|
||||
if (report->filename != NULL)
|
||||
{
|
||||
std::cout << report->filename << " (" << report->lineno << ") ";
|
||||
}
|
||||
|
||||
if (message != NULL)
|
||||
{
|
||||
std::cout << message << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "No error message available" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptingHost::Tick(float timeElapsed)
|
||||
{
|
||||
if (timeElapsed > 0.2f) timeElapsed = 0.2f;
|
||||
|
||||
for (int i = 0; i < (int)m_DelayedScripts.size(); )
|
||||
{
|
||||
m_DelayedScripts[i].m_SecondsToExecution -= timeElapsed;
|
||||
|
||||
if (m_DelayedScripts[i].m_SecondsToExecution <= 0.0f)
|
||||
{
|
||||
this->ExecuteScript(m_DelayedScripts[i].m_FunctionName);
|
||||
m_DelayedScripts.erase(m_DelayedScripts.begin() + i);
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptingHost::AddDelayedScript(const std::string & functionName, float delaySeconds)
|
||||
{
|
||||
m_DelayedScripts.push_back(DelayedScriptExecutor(functionName, delaySeconds));
|
||||
}
|
||||
|
||||
void ScriptingHost::_CollectGarbage()
|
||||
{
|
||||
JS_GC(m_Context);
|
||||
}
|
83
source/scripting/ScriptingHost.h
Executable file
83
source/scripting/ScriptingHost.h
Executable file
@ -0,0 +1,83 @@
|
||||
|
||||
#ifndef _SCRIPTINGHOST_H_
|
||||
#define _SCRIPTINGHOST_H_
|
||||
|
||||
#define XP_WIN
|
||||
#include <jsapi.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "Singleton.h"
|
||||
|
||||
class DelayedScriptExecutor
|
||||
{
|
||||
public:
|
||||
DelayedScriptExecutor(const std::string & functionName, float delaySeconds)
|
||||
: m_FunctionName(functionName), m_SecondsToExecution(delaySeconds)
|
||||
{
|
||||
}
|
||||
|
||||
std::string m_FunctionName;
|
||||
float m_SecondsToExecution;
|
||||
};
|
||||
|
||||
class CustomType
|
||||
{
|
||||
public:
|
||||
JSObject * m_Object;
|
||||
JSClass * m_Class;
|
||||
};
|
||||
|
||||
class ScriptingHost : public Singleton < ScriptingHost >
|
||||
{
|
||||
private:
|
||||
JSRuntime * m_RunTime;
|
||||
JSContext * m_Context;
|
||||
JSObject * m_GlobalObject;
|
||||
|
||||
JSErrorReport m_ErrorReport;
|
||||
|
||||
std::vector < DelayedScriptExecutor > m_DelayedScripts;
|
||||
std::map < std::string, CustomType > m_CustomObjectTypes;
|
||||
|
||||
void _CollectGarbage();
|
||||
|
||||
public:
|
||||
|
||||
ScriptingHost();
|
||||
~ScriptingHost();
|
||||
|
||||
void LoadScriptFromDisk(const std::string & fileName);
|
||||
|
||||
jsval CallFunction(const std::string & functionName, jsval * params, int numParams);
|
||||
|
||||
jsval ExecuteScript(const std::string & script);
|
||||
|
||||
void RegisterFunction(const std::string & functionName, JSNative function, int numArgs);
|
||||
|
||||
void DefineConstant(const std::string & name, int value);
|
||||
void DefineConstant(const std::string & name, double value);
|
||||
|
||||
void DefineCustomObjectType(JSClass *clasp, JSNative constructor, uintN nargs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs);
|
||||
|
||||
JSObject * CreateCustomObject(const std::string & typeName);
|
||||
|
||||
void SetObjectProperty(JSObject * object, const std::string & propertyName, jsval value);
|
||||
|
||||
int ValueToInt(const jsval value);
|
||||
bool ValueToBool(const jsval value);
|
||||
std::string ValueToString(const jsval value);
|
||||
double ValueToDouble(const jsval value);
|
||||
|
||||
void Tick(float timeElapsed);
|
||||
|
||||
void AddDelayedScript(const std::string & functionName, float delaySeconds);
|
||||
|
||||
static void ErrorReporter(JSContext * context, const char * message, JSErrorReport * report);
|
||||
};
|
||||
|
||||
#define g_ScriptingHost ScriptingHost::GetSingleton()
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user