mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-06-21 01:04:06 +00:00
Cache JS component wrappers
This commit is contained in:
committed by
wraitii
parent
0a4bfefb1e
commit
ea5a350f83
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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); \
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user