1
0
forked from mirrors/0ad

Cache JS component wrappers

This commit is contained in:
Lancelot de Ferrière
2025-01-19 15:19:25 +01:00
committed by wraitii
parent 0a4bfefb1e
commit ea5a350f83
12 changed files with 107 additions and 64 deletions
+1 -1
View File
@@ -517,7 +517,7 @@ void ScriptInterface::DefineCustomObjectType(JSClass *clasp, JSNative constructo
JSObject* ScriptInterface::CreateCustomObject(const std::string& typeName) const
{
std::map<std::string, CustomType>::const_iterator it = m_CustomObjectTypes.find(typeName);
std::unordered_map<std::string, CustomType>::const_iterator it = m_CustomObjectTypes.find(typeName);
if (it == m_CustomObjectTypes.end())
throw PSERROR_Scripting_TypeDoesNotExist();
+2 -2
View File
@@ -24,7 +24,7 @@
#include "scriptinterface/ScriptRequest.h"
#include "scriptinterface/ScriptTypes.h"
#include <map>
#include <unordered_map>
ERROR_GROUP(Scripting);
ERROR_TYPE(Scripting, SetupFailed);
@@ -289,7 +289,7 @@ private:
// members have to be called before the custom destructor of ScriptInterface_impl.
std::unique_ptr<ScriptInterface_impl> m;
std::map<std::string, CustomType> m_CustomObjectTypes;
std::unordered_map<std::string, CustomType> m_CustomObjectTypes;
};
// Explicitly instantiate void* as that is used for the generic template,
+1 -1
View File
@@ -48,7 +48,7 @@ public:
void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) final
{
deserialize.Bool("isActive", m_IsActive);
m_Script.Deserialize(paramNode, deserialize, GetEntityId());
m_Script.Deserialize(GetSimContext().GetComponentManager(), paramNode, deserialize, GetEntityId());
}
void HandleMessage(const CMessage& msg, bool global) final
@@ -35,7 +35,7 @@
#define FAIL(msg) STMT(LOGERROR(msg); return false)
#define FAIL_VOID(msg) STMT(ScriptException::Raise(rq, msg); return)
template<> void Script::ToJSVal<IComponent*>(const ScriptRequest& rq, JS::MutableHandleValue ret, IComponent* const& val)
template<> void Script::ToJSVal<IComponent*>(const ScriptRequest& UNUSED(rq), JS::MutableHandleValue ret, IComponent* const& val)
{
if (val == NULL)
{
@@ -43,18 +43,8 @@ template<> void Script::ToJSVal<IComponent*>(const ScriptRequest& rq, JS::Mutab
return;
}
// If this is a scripted component, just return the JS object directly
JS::RootedValue instance(rq.cx, val->GetJSInstance());
if (!instance.isNull())
{
ret.set(instance);
return;
}
// Otherwise we need to construct a wrapper object
// (TODO: cache wrapper objects?)
JS::RootedObject obj(rq.cx);
if (!val->NewJSObject(rq.GetScriptInterface(), &obj))
JS::HandleValue instance(val->GetJSInstance());
if (instance.isNull())
{
// Report as an error, since scripts really shouldn't try to use unscriptable interfaces
LOGERROR("IComponent does not have a scriptable interface");
@@ -62,8 +52,7 @@ template<> void Script::ToJSVal<IComponent*>(const ScriptRequest& rq, JS::Mutab
return;
}
JS::SetReservedSlot(obj, ScriptInterface::JSObjectReservedSlots::PRIVATE, JS::PrivateValue(val));
ret.setObject(*obj);
ret.set(instance);
}
template<> void Script::ToJSVal<CParamNode>(const ScriptRequest& rq, JS::MutableHandleValue ret, CParamNode const& val)
@@ -25,31 +25,31 @@
#include "scriptinterface/Object.h"
#include "simulation2/serialization/ISerializer.h"
#include "simulation2/serialization/IDeserializer.h"
#include "simulation2/system/ComponentManager.h"
CComponentTypeScript::CComponentTypeScript(const ScriptInterface& scriptInterface, JS::HandleValue instance) :
m_ScriptInterface(scriptInterface)
{
m_Instance.init(ScriptRequest(m_ScriptInterface).cx, instance);
}
m_ScriptInterface(scriptInterface), m_Instance(instance)
{}
void CComponentTypeScript::Init(const CParamNode& paramNode, entity_id_t ent)
void CComponentTypeScript::Init(CComponentManager& cmpMgr, const CParamNode& paramNode, entity_id_t ent)
{
cmpMgr.RegisterTrace(ent, m_Instance);
ScriptRequest rq(m_ScriptInterface);
Script::SetProperty(rq, m_Instance, "entity", (int)ent, true, false);
Script::SetProperty(rq, m_Instance, "template", paramNode, true, false);
ScriptFunction::CallVoid(rq, m_Instance, "Init");
Script::SetProperty(rq, GetInstance(), "entity", (int)ent, true, false);
Script::SetProperty(rq, GetInstance(), "template", paramNode, true, false);
ScriptFunction::CallVoid(rq, GetInstance(), "Init");
}
void CComponentTypeScript::Deinit()
{
ScriptRequest rq(m_ScriptInterface);
ScriptFunction::CallVoid(rq, m_Instance, "Deinit");
ScriptFunction::CallVoid(rq, GetInstance(), "Deinit");
}
bool CComponentTypeScript::HasMessageHandler(const CMessage& msg, const bool global)
{
const ScriptRequest rq(m_ScriptInterface);
return Script::HasProperty(rq, m_Instance, global ? msg.GetScriptGlobalHandlerName() :
return Script::HasProperty(rq, GetInstance(), global ? msg.GetScriptGlobalHandlerName() :
msg.GetScriptHandlerName());
}
@@ -61,7 +61,7 @@ void CComponentTypeScript::HandleMessage(const CMessage& msg, bool global)
JS::RootedValue msgVal(rq.cx, msg.ToJSValCached(rq));
if (!ScriptFunction::CallVoid(rq, m_Instance, name, msgVal))
if (!ScriptFunction::CallVoid(rq, GetInstance(), name, msgVal))
LOGERROR("Script message handler %s failed", name);
}
@@ -71,26 +71,28 @@ void CComponentTypeScript::Serialize(ISerializer& serialize)
try
{
serialize.ScriptVal("comp", &m_Instance);
serialize.ScriptVal("comp", GetMutInstance());
}
catch(PSERROR_Serialize& err)
{
int ent = INVALID_ENTITY;
Script::GetProperty(rq, m_Instance, "entity", ent);
Script::GetProperty(rq, GetInstance(), "entity", ent);
std::string name = "(error)";
Script::GetObjectClassName(rq, m_Instance, name);
LOGERROR("Script component %s of entity %i failed to serialize: %s\nSerializing:\n%s", name, ent, err.what(), Script::ToString(rq, &m_Instance));
Script::GetObjectClassName(rq, GetInstance(), name);
LOGERROR("Script component %s of entity %i failed to serialize: %s\nSerializing:\n%s", name, ent, err.what(), Script::ToString(rq, GetMutInstance()));
// Rethrow now that we added more details
throw;
}
}
void CComponentTypeScript::Deserialize(const CParamNode& paramNode, IDeserializer& deserialize, entity_id_t ent)
void CComponentTypeScript::Deserialize(CComponentManager& cmpMgr, const CParamNode& paramNode, IDeserializer& deserialize, entity_id_t ent)
{
cmpMgr.RegisterTrace(ent, m_Instance);
ScriptRequest rq(m_ScriptInterface);
Script::SetProperty(rq, m_Instance, "entity", (int)ent, true, false);
Script::SetProperty(rq, m_Instance, "template", paramNode, true, false);
Script::SetProperty(rq, GetInstance(), "entity", (int)ent, true, false);
Script::SetProperty(rq, GetInstance(), "template", paramNode, true, false);
deserialize.ScriptObjectAssign("comp", m_Instance);
deserialize.ScriptObjectAssign("comp", GetInstance());
}
+12 -10
View File
@@ -37,22 +37,24 @@ class CComponentTypeScript
public:
CComponentTypeScript(const ScriptInterface& scriptInterface, JS::HandleValue instance);
JS::Value GetInstance() const { return m_Instance.get(); }
JS::HandleValue GetInstance() const { return JS::HandleValue::fromMarkedLocation(m_Instance.address()); }
JS::MutableHandleValue GetMutInstance() { return JS::MutableHandleValue::fromMarkedLocation(const_cast<JS::Value*>(m_Instance.address())); }
static void Trace(JSTracer* trc, void* data);
void Init(const CParamNode& paramNode, entity_id_t ent);
void Init(CComponentManager& cmpMgr, const CParamNode& paramNode, entity_id_t ent);
void Deinit();
bool HasMessageHandler(const CMessage& msg, const bool global);
void HandleMessage(const CMessage& msg, bool global);
void Serialize(ISerializer& serialize);
void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize, entity_id_t ent);
void Deserialize(CComponentManager& cmpMgr, const CParamNode& paramNode, IDeserializer& deserialize, entity_id_t ent);
template<typename R, typename... Ts>
R Call(const char* funcname, const Ts&... params) const
{
R ret;
ScriptRequest rq(m_ScriptInterface);
if (ScriptFunction::Call(rq, m_Instance, funcname, ret, params...))
if (ScriptFunction::Call(rq, GetInstance(), funcname, ret, params...))
return ret;
LOGERROR("Error calling component script function %s", funcname);
return R();
@@ -63,7 +65,7 @@ public:
void CallRef(const char* funcname, R ret, const Ts&... params) const
{
ScriptRequest rq(m_ScriptInterface);
if (!ScriptFunction::Call(rq, m_Instance, funcname, ret, params...))
if (!ScriptFunction::Call(rq, GetInstance(), funcname, ret, params...))
LOGERROR("Error calling component script function %s", funcname);
}
@@ -71,13 +73,13 @@ public:
void CallVoid(const char* funcname, const Ts&... params) const
{
ScriptRequest rq(m_ScriptInterface);
if (!ScriptFunction::CallVoid(rq, m_Instance, funcname, params...))
if (!ScriptFunction::CallVoid(rq, GetInstance(), funcname, params...))
LOGERROR("Error calling component script function %s", funcname);
}
private:
const ScriptInterface& m_ScriptInterface;
JS::PersistentRootedValue m_Instance;
JS::Heap<JS::Value> m_Instance;
};
#define REGISTER_COMPONENT_SCRIPT_WRAPPER(cname) \
@@ -105,13 +107,13 @@ private:
} \
void Init(const CParamNode& paramNode) override \
{ \
m_Script.Init(paramNode, GetEntityId()); \
m_Script.Init(GetSimContext().GetComponentManager(), paramNode, GetEntityId()); \
} \
void Deinit() override \
{ \
m_Script.Deinit(); \
} \
JS::Value GetJSInstance() const override \
JS::HandleValue GetJSInstance() const override \
{ \
return m_Script.GetInstance(); \
} \
@@ -136,7 +138,7 @@ private:
} \
void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) override \
{ \
m_Script.Deserialize(paramNode, deserialize, GetEntityId()); \
m_Script.Deserialize(GetSimContext().GetComponentManager(), paramNode, deserialize, GetEntityId()); \
} \
DEFAULT_SCRIPT_WRAPPER_BASIC(cname)
@@ -68,6 +68,8 @@ CComponentManager::CComponentManager(CSimContext& context, ScriptContext& cx, bo
m_ScriptInterface.SetCallbackData(static_cast<void*> (this));
m_ScriptInterface.ReplaceNondeterministicRNG(m_RNG);
JS_AddExtraGCRootsTracer(m_ScriptInterface.GetGeneralJSContext(), Trace, (void*)this);
// 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)
@@ -119,6 +121,7 @@ CComponentManager::CComponentManager(CSimContext& context, ScriptContext& cx, bo
CComponentManager::~CComponentManager()
{
JS_RemoveExtraGCRootsTracer(m_ScriptInterface.GetGeneralJSContext(), Trace, (void*)this);
ResetState();
}
@@ -494,6 +497,9 @@ void CComponentManager::ResetState()
m_DynamicMessageSubscriptionsNonsync.clear();
m_DynamicMessageSubscriptionsNonsyncByComponent.clear();
// Remove all items we were tracing.
m_TraceCache.clear();
// Delete all IComponents in reverse order of creation.
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::reverse_iterator iit = m_ComponentsByTypeId.rbegin();
for (; iit != m_ComponentsByTypeId.rend(); ++iit)
@@ -815,6 +821,19 @@ void CComponentManager::AddMockComponent(CEntityHandle ent, InterfaceId iid, ICo
cache->interfaces[iid] = &component;
}
void CComponentManager::Trace(JSTracer* trc, void* data)
{
CComponentManager& cmpMgr = *(static_cast<CComponentManager*>(data));
for (auto& traceByEnt : cmpMgr.m_TraceCache)
for (JS::Heap<JS::Value>* ptr : traceByEnt.second)
JS::TraceEdge(trc, ptr, "ComponentManager::Trace");
}
void CComponentManager::RegisterTrace(entity_id_t ent, const JS::Heap<JS::Value>& instance)
{
m_TraceCache.try_emplace(ent).first->second.push_back(const_cast<JS::Heap<JS::Value>*>(std::addressof(instance)));
}
CEntityHandle CComponentManager::AllocateEntityHandle(entity_id_t ent)
{
ENSURE(!EntityExists(ent));
@@ -955,6 +974,10 @@ void CComponentManager::FlushDestroyedComponents()
free(handle.GetComponentCache());
m_ComponentCaches.erase(ent);
auto hit = m_TraceCache.find(ent);
if (hit != m_TraceCache.end())
m_TraceCache.erase(hit);
// Remove from m_ComponentsByInterface
std::vector<std::unordered_map<entity_id_t, IComponent*> >::iterator ifcit = m_ComponentsByInterface.begin();
for (; ifcit != m_ComponentsByInterface.end(); ++ifcit)
@@ -239,6 +239,16 @@ public:
*/
void FlushDestroyedComponents();
/**
* Called during GC tracing of our components.
*/
static void Trace(JSTracer* trc, void* data);
/**
* Call this from components when they need to save their JS instance.
* Not done by the component manager because C++ components do it lazily.
*/
void RegisterTrace(entity_id_t ent, const JS::Heap<JS::Value>& instance);
IComponent* QueryInterface(entity_id_t ent, InterfaceId iid) const;
using InterfacePair = std::pair<entity_id_t, IComponent*>;
@@ -335,6 +345,7 @@ private:
std::map<IComponent*, std::set<MessageTypeId> > m_DynamicMessageSubscriptionsNonsyncByComponent;
std::unordered_map<entity_id_t, SEntityComponentCache*> m_ComponentCaches;
std::unordered_map<entity_id_t, std::vector<JS::Heap<JS::Value>*>> m_TraceCache;
// TODO: maintaining both ComponentsBy* is nasty; can we get rid of one,
// while keeping QueryInterface and PostMessage sufficiently efficient?
-10
View File
@@ -48,13 +48,3 @@ void IComponent::RegisterComponentTypeScriptWrapper(CComponentManager& mgr, EInt
void IComponent::HandleMessage(const CMessage& UNUSED(msg), bool UNUSED(global))
{
}
bool IComponent::NewJSObject(const ScriptInterface& UNUSED(scriptInterface), JS::MutableHandleObject UNUSED(out)) const
{
return false;
}
JS::Value IComponent::GetJSInstance() const
{
return JS::NullValue();
}
+8 -4
View File
@@ -66,14 +66,18 @@ public:
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) = 0;
/**
* Returns false by default, indicating that a scripted wrapper of this IComponent is not supported.
* Derrived classes should return true if they implement such a wrapper.
* @Returns JS::NullHandleValue if a scripted wrapper of this IComponent is not supported, the wrapper otherwise.
*/
virtual bool NewJSObject(const ScriptInterface& scriptInterface, JS::MutableHandleObject out) const;
virtual JS::Value GetJSInstance() const;
virtual JS::HandleValue GetJSInstance() const = 0;
virtual int GetComponentTypeId() const = 0;
private:
/**
* @Returns whether a scripted wrapper of this IComponent is not supported.
* Derrived classes should return true if they implement such a wrapper.
*/
virtual bool NewJSObject(const ScriptInterface& scriptInterface, JS::MutableHandleObject out) const = 0;
CEntityHandle m_EntityHandle;
const CSimContext* m_SimContext;
};
+6 -2
View File
@@ -21,8 +21,12 @@
#include "simulation2/system/IComponent.h"
#define DECLARE_INTERFACE_TYPE(iname) \
virtual bool NewJSObject(const ScriptInterface& scriptInterface, JS::MutableHandleObject out) const; \
virtual JS::HandleValue GetJSInstance() const; \
static void InterfaceInit(ScriptInterface& scriptInterface); \
static EInterfaceId GetInterfaceId() { return IID_##iname; }
static EInterfaceId GetInterfaceId() { return IID_##iname; } \
private: \
virtual bool NewJSObject(const ScriptInterface& scriptInterface, JS::MutableHandleObject out) const; \
mutable JS::Heap<JS::Value> m_CachedInstance; \
public:
#endif // INCLUDED_INTERFACE
@@ -19,8 +19,10 @@
#define INCLUDED_INTERFACE_SCRIPTED
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/Object.h"
#include "scriptinterface/ScriptConversions.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/system/ComponentManager.h"
#define BEGIN_INTERFACE_WRAPPER(iname) \
JSClass class_ICmp##iname = { \
@@ -37,8 +39,24 @@
bool ICmp##iname::NewJSObject(const ScriptInterface& scriptInterface, JS::MutableHandleObject out) const\
{ \
out.set(scriptInterface.CreateCustomObject("ICmp" #iname)); \
IComponent* comp = const_cast<IComponent*>(static_cast<const IComponent*>(this)); \
JS::SetReservedSlot(out, ScriptInterface::JSObjectReservedSlots::PRIVATE, JS::PrivateValue(comp)); \
return true; \
} \
JS::HandleValue ICmp##iname::GetJSInstance() const \
{ \
if (m_CachedInstance) \
return JS::HandleValue::fromMarkedLocation(m_CachedInstance.address()); \
\
const ScriptInterface& si = GetSimContext().GetScriptInterface(); \
ScriptRequest rq(si); \
JS::RootedObject obj(rq.cx); \
NewJSObject(GetSimContext().GetScriptInterface(), &obj); \
m_CachedInstance.setObject(*obj); \
\
GetSimContext().GetComponentManager().RegisterTrace(GetEntityId(), m_CachedInstance); \
return JS::HandleValue::fromMarkedLocation(m_CachedInstance.address()); \
} \
void RegisterComponentInterface_##iname(ScriptInterface& scriptInterface) { \
ICmp##iname::InterfaceInit(scriptInterface); \
}