17 Feb, 2007, kiasyn wrote in the 1st comment:
Votes: 0
Resources used in this thread:
http://www.mozilla.org/js/spidermonkey/a...
http://www.mozilla.org/js/spidermonkey/a...
http://www.mozilla.org/js/spidermonkey/t...
http://users.skynet.be/saw/SpiderMonkey....

I am playing with embedding the spidermonkey javascript engine thingy. Just starting simple.

To get it to compile I have done:

<debian>apt-get install libsmjs-dev libsmjs1</debian>
added -lsmjs to L_FLAGS in Makefile
added -DXP_UNIX to C_FLAGS in Makefile

My js.cpp file looks like:

#include <smjs/jsapi.h>
#include <string.h>
#include "mud.h"


void js_error_reporter( JSContext *cx, const char *message, JSErrorReport *report )
{
bug( "JS Error: %s", message );
}
CMDF do_testjs( Character *ch, char *argument )
{
JSRuntime *rt;
JSContext *cx;
JSObject *global;
JSClass global_class = {
"global",0,
JS_PropertyStub,JS_PropertyStub,JS_PropertyStub,JS_PropertyStub,
JS_EnumerateStub,JS_ResolveStub,JS_ConvertStub,JS_FinalizeStub
};

/*
* You always need:
* a runtime per process,
* a context per thread,
* a global object per context,
* standard classes (e.g. Date).
*/
rt = JS_NewRuntime(0x100000);
cx = JS_NewContext(rt, 0x1000);
global = JS_NewObject(cx, &global_class, NULL, NULL);
JS_InitStandardClasses(cx, global);

JS_SetErrorReporter( cx, &js_error_reporter );
/*
* Now suppose script contains some JS to evaluate, say "22/7" as a
* bad approximation for Math.PI, or something longer, such as this:
* "(function fact(n){if (n <= 1) return 1; return n * fact(n-1)})(5)"
* to compute 5!
*/
char *script = "var cheese = 1;";
jsval rval;
JSString *str;
JSBool ok;

if ( ( ok = JS_EvaluateScript(cx, global, script, strlen(script), "Drazon", 0, &rval) ) == JS_FALSE )
ch_printf( ch, "There was an error\r\n" );
else
{
str = JS_ValueToString(cx, rval);
ch_printf( ch, "script result: %s\n", JS_GetStringBytes(str));
}
JS_ClearScope( cx, global);
JS_GC( cx );
JS_DestroyContext( cx );
JS_DestroyRuntime( rt );
}


Will be posting more as I go :)
17 Feb, 2007, kiasyn wrote in the 2nd comment:
Votes: 0
Beefing up the error reporter to show line number:

void js_error_reporter( JSContext *cx, const char *message, JSErrorReport *err )
{
bug( "JS Error: [%s:%d] %s", err->filename, err->lineno, message );
}


I have added a function that can be called from within the engine. For simplicity I have gone with the bug function.

JSBool js_bug( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval )
{
if ( argc < 1 )
{
/* No arguments, do nothing */
*rval = INT_TO_JSVAL(0);
return JS_TRUE;
}
string out;
/* a handy cheat - allows us to concat arguments, ie:
bug( "Hi ", name, ". How are you?" );
*/
for ( uintN i = 0; i < argc; i++ )
{
JSString *val = JS_ValueToString( cx, argv[i] );
out += JS_GetStringBytes(val);
}
bug( "%s", out.c_str() );
/* return the length of the string posted to bug() */
*rval = INT_TO_JSVAL( out.length() );
return JS_TRUE;
}


To get the engine to recognize this function I add the following code below JS_InitStandardClasses(cx, global);

if ( JS_DefineFunction( cx, global, "bug", js_bug, 1, 0 ) == JS_FALSE )
{
ch_printf( ch, "Error defining bug function.\r\n" );
goto end;
}


and thats it. I can now call bug( ); from within a script.

My complete file now reads:

#include <smjs/jsapi.h>
#include <string>
#include "mud.h"
using namespace std;

void js_error_reporter( JSContext *cx, const char *message, JSErrorReport *err )
{
bug( "JS Error: [%s:%d] %s", err->filename, err->lineno, message );
}
JSBool js_bug( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval )
{
if ( argc < 1 )
{
/* No arguments, do nothing */
*rval = INT_TO_JSVAL(0);
return JS_TRUE;
}
string out;
/* a handy cheat - allows us to concat arguments, ie:
bug( "Hi ", name, ". How are you?" );
*/
for ( uintN i = 0; i < argc; i++ )
{
JSString *val = JS_ValueToString( cx, argv[i] );
out += JS_GetStringBytes(val);
}
bug( "%s", out.c_str() );
*rval = INT_TO_JSVAL( out.length() );
return JS_TRUE;
}
CMDF do_testjs( Character *ch, char *argument )
{
JSRuntime *rt;
JSContext *cx;
JSObject *global;
JSClass global_class = {
"global",0,
JS_PropertyStub,JS_PropertyStub,JS_PropertyStub,JS_PropertyStub,
JS_EnumerateStub,JS_ResolveStub,JS_ConvertStub,JS_FinalizeStub
};
const char *script = argument[0] == '\0' ? "bug( \"undefined argument to testjs\" );" : argument;
jsval rval;
JSString *str;
JSBool ok;
/*
* You always need:
* a runtime per process,
* a context per thread,
* a global object per context,
* standard classes (e.g. Date).
*/
rt = JS_NewRuntime(0x100000);
cx = JS_NewContext(rt, 0x1000);
global = JS_NewObject(cx, &global_class, NULL, NULL);
JS_InitStandardClasses(cx, global);

if ( JS_DefineFunction( cx, global, "bug", js_bug, 1, 0 ) == JS_FALSE )
{
ch_printf( ch, "Error defining bug function.\r\n" );
goto end;
}
JS_SetErrorReporter( cx, &js_error_reporter );
/*
* Now suppose script contains some JS to evaluate, say "22/7" as a
* bad approximation for Math.PI, or something longer, such as this:
* "(function fact(n){if (n <= 1) return 1; return n * fact(n-1)})(5)"
* to compute 5!
*/

if ( ( ok = JS_EvaluateScript(cx, global, script, strlen(script), "Drazon", 0, &rval) ) == JS_FALSE )
ch_printf( ch, "There was an error\r\n" );
else
{
str = JS_ValueToString(cx, rval);
ch_printf( ch, "script result: %s\n", JS_GetStringBytes(str));
}
JS_ClearScope( cx, global);
JS_GC( cx );
end:
JS_DestroyContext( cx );
JS_DestroyRuntime( rt );
}
17 Feb, 2007, kiasyn wrote in the 3rd comment:
Votes: 0
A bigger update here.

Wrote a javascript wrapper class for Room.

File below:

#include <smjs/jsapi.h>
#include <string>
#include "mud.h"
using namespace std;

void js_error_reporter( JSContext *cx, const char *message, JSErrorReport *err )
{
bug( "JS Error: [%s:%d] %s", err->filename, err->lineno, message );
}
JSBool js_bug( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval )
{
if ( argc < 1 )
{
/* No arguments, do nothing */
*rval = INT_TO_JSVAL(0);
return JS_TRUE;
}
string out;
/* a handy cheat - allows us to concat arguments, ie:
bug( "Hi ", name, ". How are you?" );
*/
for ( uintN i = 0; i < argc; i++ )
{
JSString *val = JS_ValueToString( cx, argv[i] );
out += JS_GetStringBytes(val);
}
bug( "%s", out.c_str() );
*rval = INT_TO_JSVAL( out.length() );
return JS_TRUE;
}
class JSRoom
{
public:
Room *room;
JSRoom() : room(NULL)
{
};
~JSRoom()
{
if ( room )
delete room;
room = NULL;
}
enum
{
vnum_prop,
name_prop
};
static JSPropertySpec room_properties[];
static JSFunctionSpec room_methods[];
static JSObject *JSInit( JSContext *cx, JSObject *obj, JSObject *proto )
{
return JS_InitClass( cx, obj, proto, &roomClass,
JSRoom::JSConstructor, 0,
JSRoom::room_properties, JSRoom::room_methods, NULL, NULL);
};
static JSBool JSConstructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval )
{
int vnum = -1;
Area *area;
if ( argc < 2 )
return JS_FALSE;
if ( JS_ValueToInt32( cx, argv[0], &vnum ) == JS_FALSE )
return JS_FALSE;
if ( get_room_index( vnum ) )
return JS_FALSE;
area = get_area( JS_GetStringBytes( JS_ValueToString( cx, argv[1] ) ) );
JSRoom *r = new JSRoom();
r->room = new Room( vnum, area );
r->room->name = STRALLOC( "Floating in a void" );
r->room->description = STRALLOC( "" );

if ( !JS_SetPrivate( cx, obj, r ) )
return JS_FALSE;
*rval = OBJECT_TO_JSVAL( obj );
return JS_TRUE;
};
static JSBool JSGetProperty( JSContext *cx, JSObject *obj, jsval id, jsval *vp )
{
if ( JSVAL_IS_INT(id) )
{
JSRoom *r = (JSRoom *)JS_GetPrivate( cx, obj );
switch ( JSVAL_TO_INT(id) )
{
case vnum_prop: *vp = INT_TO_JSVAL( r->room->vnum ); break;
case name_prop: break;
}
}
return JS_TRUE;
};
static JSBool JSSetProperty( JSContext *cx, JSObject *obj, jsval id, jsval *vp )
{
if ( JSVAL_IS_INT(id) )
{
JSRoom *r = (JSRoom *)JS_GetPrivate( cx, obj );
switch( JSVAL_TO_INT(id) )
{
case vnum_prop: break;
case name_prop:
STRFREE( r->room->name );
r->room->name = STRALLOC( JS_GetStringBytes( JS_ValueToString( cx, *vp ) ) );
break;
}
}
return JS_TRUE;
};
static void JSDestructor( JSContext *cx, JSObject *obj )
{
JSRoom *r = (JSRoom *)JS_GetPrivate( cx, obj );
delete r;
r = NULL;
};
static JSClass roomClass;
};
JSClass JSRoom::roomClass = {
"Room", JSCLASS_HAS_PRIVATE,
JS_PropertyStub, JS_PropertyStub,
JSRoom::JSGetProperty, JSRoom::JSSetProperty,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, JSRoom::JSDestructor
};
JSPropertySpec JSRoom::room_properties[] =
{
{ "vnum", vnum_prop, JSPROP_ENUMERATE | JSPROP_READONLY },
{ "name", name_prop, JSPROP_ENUMERATE },
{ NULL }
};
JSFunctionSpec JSRoom::room_methods[] =
{
{ NULL }
};
CMDF do_testjs( Character *ch, char *argument )
{
JSRuntime *rt;
JSContext *cx;
JSObject *global;
JSClass global_class = {
"global",0,
JS_PropertyStub,JS_PropertyStub,JS_PropertyStub,JS_PropertyStub,
JS_EnumerateStub,JS_ResolveStub,JS_ConvertStub,JS_FinalizeStub
};
const char *script = argument[0] == '\0' ? "bug( \"undefined argument to testjs\" );" : argument;
jsval rval;
JSString *str;
JSBool ok;
/*
* You always need:
* a runtime per process,
* a context per thread,
* a global object per context,
* standard classes (e.g. Date).
*/
rt = JS_NewRuntime(0x100000);
cx = JS_NewContext(rt, 0x1000);
global = JS_NewObject(cx, &global_class, NULL, NULL);
JS_InitStandardClasses(cx, global);
JSRoom::JSInit( cx, global, NULL );
if ( JS_DefineFunction( cx, global, "bug", js_bug, 1, 0 ) == JS_FALSE )
{
ch_printf( ch, "Error defining bug function.\r\n" );
goto end;
}
JS_SetErrorReporter( cx, &js_error_reporter );
/*
* Now suppose script contains some JS to evaluate, say "22/7" as a
* bad approximation for Math.PI, or something longer, such as this:
* "(function fact(n){if (n <= 1) return 1; return n * fact(n-1)})(5)"
* to compute 5!
*/

if ( ( ok = JS_EvaluateScript(cx, global, script, strlen(script), "Drazon", 0, &rval) ) == JS_FALSE )
ch_printf( ch, "There was an error\r\n" );
else
{
str = JS_ValueToString(cx, rval);
ch_printf( ch, "script result: %s\n", JS_GetStringBytes(str));
}
JS_ClearScope( cx, global);
JS_GC( cx );
end:
JS_DestroyContext( cx );
JS_DestroyRuntime( rt );
}
17 Feb, 2007, kiasyn wrote in the 4th comment:
Votes: 0
Some handy macros (I keep them in js.h)

#define JS_STRALLOC_STRING( str, cx, vp )	\
if ( (str) ) \
STRFREE( (str) ); \
(str) = STRALLOC( JS_GetStringBytes( JS_ValueToString( (cx), (vp) ) ) );

#define JS_CSTR_TO_JSVAL( str, cx ) \
STRING_TO_JSVAL( JS_NewStringCopyN( (cx), (str), strlen((str)) ) );
17 Feb, 2007, kiasyn wrote in the 5th comment:
Votes: 0
Defining a global object (in this case game)
JSObject *game = NULL;
if ( ( game = JS_DefineObject( cx, global, "game", &JSGame::gameClass, NULL, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE ) ) == NULL )
{
ch_printf( ch, "Error defining game object.\r\n" );
goto end;
}
17 Feb, 2007, kiasyn wrote in the 6th comment:
Votes: 0
Returning an JS object (Character in this case) from a function.

Used in Room class (Room.getCharacter).
static JSBool getCharacter( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval )
{
if ( argc != 1 || ! JSVAL_IS_STRING(argv[0]) )
return JS_FALSE;
char *name = JS_JVAL_TO_CSTR( argv[0] );
JSRoom *r = (JSRoom *)JS_GetPrivate( cx, obj );
for ( Character *ch = r->room->first_person; ch; ch = ch->next_in_room )
if ( !str_cmp( ch->name, name ) )
{
JSCharacter *character = new JSCharacter;
JSObject *chobj = JS_NewObject( cx, &JSCharacter::characterClass, NULL, NULL );
character->character = ch;
if ( !JS_SetPrivate( cx, chobj, character ) )
return JS_FALSE;
*rval = OBJECT_TO_JSVAL( chobj );
return JS_TRUE;
}
return JS_FALSE;
}
18 Feb, 2007, Justice wrote in the 7th comment:
Votes: 0
Just wanted to make a few comments based on my experience.

You only need one runtime per application, and one context per thread. The "global" object holds the scope of the script, detailing what variables, functions, objects, etc are available. Any JSObject may be used as the "global" object, to understand how this works, you can look at javascript's 'with' statement.

In my code, I have an init and cleanup function that handles creating and cleaning up the runtime and context. Every script in the game uses the same context. With the exception of included scripts, every script runs with a fresh "global" object that is GC'd after use. Included scripts execute with the same scope (ie global object) so that any resources will be available to scripts running afterward.

Also, it's possible to create a hard reference to a JSObject. This prevents the GC from reclaiming the memory until the reference is released. This may be useful if you want to allow the virtual JS state to be maintained across script executions.
18 Feb, 2007, Justice wrote in the 8th comment:
Votes: 0
Oh yeah, and here's a little gotcha in spidermoney. INT_TO_JSVAL create's a signed 31bit integer. That means it becomes negative at 1073741824 instead of 2147483648. To avoid this, you need to use a JSDouble.

I don't have a set version of it, but here's what I use to convert things like ch->gold into JS:
#define JS_SET_DBL_VAL(cx, ret, a)                       \
{ \
jsdouble dbl = (jsdouble)a; \
*ret = DOUBLE_TO_JSVAL(JS_NewDouble(cx, dbl)); \
}
02 Mar, 2007, kiasyn wrote in the 9th comment:
Votes: 0
A fun function I created for making a JS object out of a value (eg, Character *ch )

template<typename JSContainer, class ValueType>
JSObject *makeJSObject( JSContext *cx, ValueType value )
{
if ( !value )
return JSVAL_NULL;
JSContainer *cont = new JSContainer;
JSObject *jsobj = JS_NewObject( cx, &JSContainer::objectDefs, NULL, NULL );
cont->object = value;
if ( !JS_SetPrivate( cx, jsobj, cont ) )
{
delete cont;
return NULL;
}
return jsobj;
}


Usage:
JSObject *obj = makeJSObject<JSCharacter>( cx, ch );


where JSCharacter is the wrapper class for your object.

Requires the wrapper class to have a ValueType *object pointer and the JSClass definiton to be named objectDefs.
04 May, 2007, kiasyn wrote in the 10th comment:
Votes: 0
0.0/10