1
0
forked from 0ad/0ad

Make Math.random() more deterministic

This was SVN commit r7563.
This commit is contained in:
Ykkrosh 2010-05-22 00:38:33 +00:00
parent 1fc75e7605
commit 386f322b1c
6 changed files with 95 additions and 0 deletions

View File

@ -49,6 +49,16 @@ template<> bool ScriptInterface::FromJSVal<float>(JSContext* cx, jsval v, float&
return true;
}
template<> bool ScriptInterface::FromJSVal<double>(JSContext* cx, jsval v, double& out)
{
jsdouble ret;
WARN_IF_NOT(JSVAL_IS_NUMBER(v));
if (!JS_ValueToNumber(cx, v, &ret))
return false;
out = ret;
return true;
}
template<> bool ScriptInterface::FromJSVal<i32>(JSContext* cx, jsval v, i32& out)
{
int32 ret;
@ -146,6 +156,13 @@ template<> jsval ScriptInterface::ToJSVal<float>(JSContext* cx, const float& val
return rval;
}
template<> jsval ScriptInterface::ToJSVal<double>(JSContext* cx, const double& val)
{
jsval rval = JSVAL_VOID;
JS_NewNumberValue(cx, val, &rval); // ignore return value
return rval;
}
template<> jsval ScriptInterface::ToJSVal<i32>(JSContext* cx, const i32& val)
{
if (INT_FITS_IN_JSVAL(val))

View File

@ -29,6 +29,9 @@
#include <boost/preprocessor/punctuation/comma_if.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/random/linear_congruential.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/random/variate_generator.hpp>
#include "valgrind.h"
@ -110,6 +113,28 @@ JSBool print(JSContext* cx, JSObject* UNUSED(obj), uintN argc, jsval* argv, jsva
return JS_TRUE;
}
// Math override functions:
JSBool Math_random(JSContext* cx, uintN UNUSED(argc), jsval* vp)
{
// Grab the RNG that was hidden in our slot
jsval rngp;
if (!JS_GetReservedSlot(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)), 0, &rngp))
return JS_FALSE;
boost::rand48* rng = static_cast<boost::rand48*>(JSVAL_TO_PRIVATE(rngp));
// TODO: is the double generation sufficiently deterministic for us?
boost::uniform_real<double> dist;
boost::variate_generator<boost::rand48&, boost::uniform_real<> > gen(*rng, dist);
double r = gen();
jsval rv;
if (!JS_NewNumberValue(cx, r, &rv))
return JS_FALSE;
JS_SET_RVAL(cx, vp, rv);
return JS_TRUE;
}
} // anonymous namespace
#if ENABLE_SCRIPT_PROFILING
@ -233,6 +258,26 @@ void* ScriptInterface::GetCallbackData(JSContext* cx)
return JS_GetContextPrivate(cx);
}
void ScriptInterface::ReplaceNondeterministicFunctions(boost::rand48& rng)
{
jsval math;
if (!JS_GetProperty(m->m_cx, m->m_glob, "Math", &math) || !JSVAL_IS_OBJECT(math))
{
LOGERROR(L"ReplaceNondeterministicFunctions: failed to get Math");
return;
}
JSFunction* random = JS_DefineFunction(m->m_cx, JSVAL_TO_OBJECT(math), "random", (JSNative)Math_random, 0,
JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSFUN_FAST_NATIVE);
if (!random)
{
LOGERROR(L"ReplaceNondeterministicFunctions: failed to replace Math.random");
return;
}
// Store the RNG in a slot which is sort-of-guaranteed to be unused by the JS engine
JS_SetReservedSlot(m->m_cx, JS_GetFunctionObject(random), 0, PRIVATE_TO_JSVAL(&rng));
}
void ScriptInterface::Register(const char* name, JSNative fptr, size_t nargs)
{
m->Register(name, fptr, (uintN)nargs);

View File

@ -29,6 +29,8 @@
#include "ScriptTypes.h"
#include "ScriptVal.h"
namespace boost { class rand48; }
// Set the maximum number of function arguments that can be handled
// (This should be as small as possible (for compiler efficiency),
// but as large as necessary for all wrapped functions)
@ -62,6 +64,8 @@ public:
JSContext* GetContext() const;
void ReplaceNondeterministicFunctions(boost::rand48& rng);
/**
* Call a constructor function, roughly equivalent to JS "new ctor".
*

View File

@ -21,6 +21,8 @@
#include "ps/CLogger.h"
#include <boost/random/linear_congruential.hpp>
class TestScriptInterface : public CxxTest::TestSuite
{
public:
@ -103,4 +105,24 @@ public:
TS_ASSERT(script2.CallFunction(obj2.get(), "toSource", source));
TS_ASSERT_STR_EQUALS(source, "({a:#1=[#1#], b:#1#})");
}
void test_random()
{
ScriptInterface script("Test");
double d1, d2;
TS_ASSERT(script.Eval("Math.random()", d1));
TS_ASSERT(script.Eval("Math.random()", d2));
TS_ASSERT_DIFFERS(d1, d2);
boost::rand48 rng;
script.ReplaceNondeterministicFunctions(rng);
rng.seed(0);
TS_ASSERT(script.Eval("Math.random()", d1));
TS_ASSERT(script.Eval("Math.random()", d2));
TS_ASSERT_DIFFERS(d1, d2);
rng.seed(0);
TS_ASSERT(script.Eval("Math.random()", d2));
TS_ASSERT_EQUALS(d1, d2);
}
};

View File

@ -36,6 +36,9 @@ CComponentManager::CComponentManager(CSimContext& context, bool skipScriptFuncti
m_ScriptInterface.SetCallbackData(static_cast<void*> (this));
// TODO: ought to seed the RNG (in a network-synchronised way) before we use it
m_ScriptInterface.ReplaceNondeterministicFunctions(m_RNG);
// For component script tests, the test system sets up its own scripted implementation of
// these functions, so we skip registering them here in those cases
if (!skipScriptFunctions)

View File

@ -22,6 +22,8 @@
#include "Components.h"
#include "scriptinterface/ScriptInterface.h"
#include <boost/random/linear_congruential.hpp>
#include <map>
class IComponent;
@ -250,6 +252,8 @@ private:
entity_id_t m_NextEntityId;
entity_id_t m_NextLocalEntityId;
boost::rand48 m_RNG;
friend class TestComponentManager;
};