Index: ps/trunk/source/simulation2/MessageTypes.h =================================================================== --- ps/trunk/source/simulation2/MessageTypes.h (revision 27727) +++ ps/trunk/source/simulation2/MessageTypes.h (revision 27728) @@ -1,606 +1,606 @@ /* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_MESSAGETYPES #define INCLUDED_MESSAGETYPES #include "simulation2/system/Components.h" #include "simulation2/system/Entity.h" #include "simulation2/system/Message.h" #include "simulation2/helpers/Player.h" #include "simulation2/helpers/Position.h" #include "simulation2/components/ICmpPathfinder.h" #include "maths/Vector3D.h" #include "ps/CStr.h" #define DEFAULT_MESSAGE_IMPL(name) \ virtual int GetType() const { return MT_##name; } \ virtual const char* GetScriptHandlerName() const { return "On" #name; } \ virtual const char* GetScriptGlobalHandlerName() const { return "OnGlobal" #name; } \ - virtual JS::Value ToJSVal(const ScriptInterface& scriptInterface) const; \ - static CMessage* FromJSVal(const ScriptInterface&, JS::HandleValue val); + virtual JS::Value ToJSVal(const ScriptRequest& rq) const; \ + static CMessage* FromJSVal(const ScriptRequest&, JS::HandleValue val); class SceneCollector; class CFrustum; class CMessageTurnStart final : public CMessage { public: DEFAULT_MESSAGE_IMPL(TurnStart) CMessageTurnStart() { } }; // The update process is split into a number of phases, in an attempt // to cope with dependencies between components. Each phase is implemented // as a separate message. Simulation2.cpp sends them in sequence. /** * Generic per-turn update message, for things that don't care much about ordering. */ class CMessageUpdate final : public CMessage { public: DEFAULT_MESSAGE_IMPL(Update) CMessageUpdate(fixed turnLength) : turnLength(turnLength) { } fixed turnLength; }; /** * Update phase for formation controller movement (must happen before individual * units move to follow their formation). */ class CMessageUpdate_MotionFormation final : public CMessage { public: DEFAULT_MESSAGE_IMPL(Update_MotionFormation) CMessageUpdate_MotionFormation(fixed turnLength) : turnLength(turnLength) { } fixed turnLength; }; /** * Update phase for non-formation-controller unit movement. */ class CMessageUpdate_MotionUnit final : public CMessage { public: DEFAULT_MESSAGE_IMPL(Update_MotionUnit) CMessageUpdate_MotionUnit(fixed turnLength) : turnLength(turnLength) { } fixed turnLength; }; /** * Final update phase, after all other updates. */ class CMessageUpdate_Final final : public CMessage { public: DEFAULT_MESSAGE_IMPL(Update_Final) CMessageUpdate_Final(fixed turnLength) : turnLength(turnLength) { } fixed turnLength; }; /** * Prepare for rendering a new frame (set up model positions etc). */ class CMessageInterpolate final : public CMessage { public: DEFAULT_MESSAGE_IMPL(Interpolate) CMessageInterpolate(float deltaSimTime, float offset, float deltaRealTime) : deltaSimTime(deltaSimTime), offset(offset), deltaRealTime(deltaRealTime) { } /// Elapsed simulation time since previous interpolate, in seconds. This is similar to the elapsed real time, except /// it is scaled by the current simulation rate (and might indeed be zero). float deltaSimTime; /// Range [0, 1] (inclusive); fractional time of current frame between previous/next simulation turns. float offset; /// Elapsed real time since previous interpolate, in seconds. float deltaRealTime; }; /** * Add renderable objects to the scene collector. * Called after CMessageInterpolate. */ class CMessageRenderSubmit final : public CMessage { public: DEFAULT_MESSAGE_IMPL(RenderSubmit) CMessageRenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling) : collector(collector), frustum(frustum), culling(culling) { } SceneCollector& collector; const CFrustum& frustum; bool culling; }; /** * Handle progressive loading of resources. * A component that listens to this message must do the following: * - Increase *msg.total by the non-zero number of loading tasks this component can perform. * - If *msg.progressed == true, return and do nothing. * - If you've loaded everything, increase *msg.progress by the value you added to .total * - Otherwise do some loading, set *msg.progressed = true, and increase *msg.progress by a * value indicating how much progress you've made in total (0 <= p <= what you added to .total) * In some situations these messages will never be sent - components must ensure they * load all their data themselves before using it in that case. */ class CMessageProgressiveLoad final : public CMessage { public: DEFAULT_MESSAGE_IMPL(ProgressiveLoad) CMessageProgressiveLoad(bool* progressed, int* total, int* progress) : progressed(progressed), total(total), progress(progress) { } bool* progressed; int* total; int* progress; }; /** * Broadcast after the entire simulation state has been deserialized. * Components should do all their self-contained work in their Deserialize * function when possible. But any reinitialisation that depends on other * components or other entities, that might not be constructed until later * in the deserialization process, may be done in response to this message * instead. */ class CMessageDeserialized final : public CMessage { public: DEFAULT_MESSAGE_IMPL(Deserialized) CMessageDeserialized() { } }; /** * This is sent immediately after a new entity's components have all been created * and initialised. */ class CMessageCreate final : public CMessage { public: DEFAULT_MESSAGE_IMPL(Create) CMessageCreate(entity_id_t entity) : entity(entity) { } entity_id_t entity; }; /** * This is sent immediately before a destroyed entity is flushed and really destroyed. * (That is, after CComponentManager::DestroyComponentsSoon and inside FlushDestroyedComponents). * The entity will still exist at the time this message is sent. * It's possible for this message to be sent multiple times for one entity, but all its components * will have been deleted after the first time. */ class CMessageDestroy final : public CMessage { public: DEFAULT_MESSAGE_IMPL(Destroy) CMessageDestroy(entity_id_t entity) : entity(entity) { } entity_id_t entity; }; class CMessageOwnershipChanged final : public CMessage { public: DEFAULT_MESSAGE_IMPL(OwnershipChanged) CMessageOwnershipChanged(entity_id_t entity, player_id_t from, player_id_t to) : entity(entity), from(from), to(to) { } entity_id_t entity; player_id_t from; player_id_t to; }; /** * Sent by CCmpPosition whenever anything has changed that will affect the * return value of GetPosition2D() or GetRotation().Y * * If @c inWorld is false, then the other fields are invalid and meaningless. * Otherwise they represent the current position. */ class CMessagePositionChanged final : public CMessage { public: DEFAULT_MESSAGE_IMPL(PositionChanged) CMessagePositionChanged(entity_id_t entity, bool inWorld, entity_pos_t x, entity_pos_t z, entity_angle_t a) : entity(entity), inWorld(inWorld), x(x), z(z), a(a) { } entity_id_t entity; bool inWorld; entity_pos_t x, z; entity_angle_t a; }; /** * Sent by CCmpPosition whenever anything has changed that will affect the * return value of GetInterpolatedTransform() */ class CMessageInterpolatedPositionChanged final : public CMessage { public: DEFAULT_MESSAGE_IMPL(InterpolatedPositionChanged) CMessageInterpolatedPositionChanged(entity_id_t entity, bool inWorld, const CVector3D& pos0, const CVector3D& pos1) : entity(entity), inWorld(inWorld), pos0(pos0), pos1(pos1) { } entity_id_t entity; bool inWorld; CVector3D pos0; CVector3D pos1; }; /** * Sent by CCmpUnitMotion during Update if an event happened that might interest other components. */ class CMessageMotionUpdate final : public CMessage { public: DEFAULT_MESSAGE_IMPL(MotionUpdate) enum UpdateType { LIKELY_SUCCESS, // UnitMotion considers it is arrived at destination. LIKELY_FAILURE, // UnitMotion says it cannot reach the destination. OBSTRUCTED, // UnitMotion was obstructed. This does not mean stuck, but can be a hint to run range checks. VERY_OBSTRUCTED, // Sent when obstructed several time in a row. LENGTH }; static const std::array UpdateTypeStr; CMessageMotionUpdate(UpdateType ut) : updateType(ut) { } UpdateType updateType; }; /** * Sent when water height has been changed. */ class CMessageWaterChanged final : public CMessage { public: DEFAULT_MESSAGE_IMPL(WaterChanged) CMessageWaterChanged() { } }; /** * Sent when terrain (texture or elevation) has been changed. */ class CMessageTerrainChanged final : public CMessage { public: DEFAULT_MESSAGE_IMPL(TerrainChanged) CMessageTerrainChanged(int32_t i0, int32_t j0, int32_t i1, int32_t j1) : i0(i0), j0(j0), i1(i1), j1(j1) { } int32_t i0, j0, i1, j1; // inclusive lower bound, exclusive upper bound, in tiles }; /** * Sent, at most once per turn, when the visibility of an entity changed */ class CMessageVisibilityChanged final : public CMessage { public: DEFAULT_MESSAGE_IMPL(VisibilityChanged) CMessageVisibilityChanged(player_id_t player, entity_id_t ent, int oldVisibility, int newVisibility) : player(player), ent(ent), oldVisibility(oldVisibility), newVisibility(newVisibility) { } player_id_t player; entity_id_t ent; int oldVisibility; int newVisibility; }; /** * Sent when then obstruction of an entity has changed in a manner * that changes 'block movement' properties. */ class CMessageMovementObstructionChanged final : public CMessage { public: DEFAULT_MESSAGE_IMPL(MovementObstructionChanged) CMessageMovementObstructionChanged() { } }; /** * Sent when ObstructionManager's view of the shape of the world has changed * (changing the TILE_OUTOFBOUNDS tiles returned by Rasterise). */ class CMessageObstructionMapShapeChanged final : public CMessage { public: DEFAULT_MESSAGE_IMPL(ObstructionMapShapeChanged) CMessageObstructionMapShapeChanged() { } }; /** * Sent when territory assignments have changed. */ class CMessageTerritoriesChanged final : public CMessage { public: DEFAULT_MESSAGE_IMPL(TerritoriesChanged) CMessageTerritoriesChanged() { } }; /** * Sent by CCmpRangeManager at most once per turn, when an active range query * has had matching units enter/leave the range since the last RangeUpdate. */ class CMessageRangeUpdate final : public CMessage { public: DEFAULT_MESSAGE_IMPL(RangeUpdate) u32 tag; std::vector added; std::vector removed; // CCmpRangeManager wants to store a vector of messages and wants to // swap vectors instead of copying (to save on memory allocations), // so add some constructors for it: // don't init tag in empty ctor CMessageRangeUpdate() { } CMessageRangeUpdate(u32 tag) : tag(tag) { } CMessageRangeUpdate(u32 tag, const std::vector& added, const std::vector& removed) : tag(tag), added(added), removed(removed) { } CMessageRangeUpdate(const CMessageRangeUpdate& other) : CMessage(), tag(other.tag), added(other.added), removed(other.removed) { } CMessageRangeUpdate& operator=(const CMessageRangeUpdate& other) { tag = other.tag; added = other.added; removed = other.removed; return *this; } }; /** * Sent by CCmpPathfinder after async path requests. */ class CMessagePathResult final : public CMessage { public: DEFAULT_MESSAGE_IMPL(PathResult) CMessagePathResult(u32 ticket, const WaypointPath& path) : ticket(ticket), path(path) { } u32 ticket; WaypointPath path; }; /** * Sent by aura manager when a value of a certain entity's component is changed */ class CMessageValueModification final : public CMessage { public: DEFAULT_MESSAGE_IMPL(ValueModification) CMessageValueModification(const std::vector& entities, std::wstring component, const std::vector& valueNames) : entities(entities), component(component), valueNames(valueNames) { } std::vector entities; std::wstring component; std::vector valueNames; }; /** * Sent by atlas if the playercolor has been changed. */ class CMessagePlayerColorChanged final : public CMessage { public: DEFAULT_MESSAGE_IMPL(PlayerColorChanged) CMessagePlayerColorChanged(player_id_t player) : player(player) { } player_id_t player; }; /** * Sent by aura and tech managers when a value of a certain template's component is changed */ class CMessageTemplateModification final : public CMessage { public: DEFAULT_MESSAGE_IMPL(TemplateModification) CMessageTemplateModification(player_id_t player, std::wstring component, const std::vector& valueNames) : player(player), component(component), valueNames(valueNames) { } player_id_t player; std::wstring component; std::vector valueNames; }; /** * Sent by CCmpVision when an entity's vision range changes. */ class CMessageVisionRangeChanged final : public CMessage { public: DEFAULT_MESSAGE_IMPL(VisionRangeChanged) CMessageVisionRangeChanged(entity_id_t entity, entity_pos_t oldRange, entity_pos_t newRange) : entity(entity), oldRange(oldRange), newRange(newRange) { } entity_id_t entity; entity_pos_t oldRange; entity_pos_t newRange; }; /** * Sent by CCmpVision when an entity's vision sharing changes. */ class CMessageVisionSharingChanged final : public CMessage { public: DEFAULT_MESSAGE_IMPL(VisionSharingChanged) CMessageVisionSharingChanged(entity_id_t entity, player_id_t player, bool add) : entity(entity), player(player), add(add) { } entity_id_t entity; player_id_t player; bool add; }; /** * Sent when an entity pings the minimap */ class CMessageMinimapPing final : public CMessage { public: DEFAULT_MESSAGE_IMPL(MinimapPing) CMessageMinimapPing() { } }; /** * Cinematics events */ class CMessageCinemaPathEnded final : public CMessage { public: DEFAULT_MESSAGE_IMPL(CinemaPathEnded) CMessageCinemaPathEnded(CStrW name) : name(name) { } CStrW name; }; class CMessageCinemaQueueEnded final : public CMessage { public: DEFAULT_MESSAGE_IMPL(CinemaQueueEnded) }; #endif // INCLUDED_MESSAGETYPES Index: ps/trunk/source/simulation2/scripting/MessageTypeConversions.cpp =================================================================== --- ps/trunk/source/simulation2/scripting/MessageTypeConversions.cpp (revision 27727) +++ ps/trunk/source/simulation2/scripting/MessageTypeConversions.cpp (revision 27728) @@ -1,563 +1,560 @@ /* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ps/CLogger.h" #include "scriptinterface/ScriptConversions.h" #include "simulation2/MessageTypes.h" #define TOJSVAL_SETUP() \ - ScriptRequest rq(scriptInterface); \ JS::RootedObject obj(rq.cx, JS_NewPlainObject(rq.cx)); \ if (!obj) \ return JS::UndefinedValue(); #define SET_MSG_PROPERTY(name) \ do { \ JS::RootedValue prop(rq.cx);\ Script::ToJSVal(rq, &prop, this->name); \ if (! JS_SetProperty(rq.cx, obj, #name, prop)) \ return JS::UndefinedValue(); \ } while (0); #define FROMJSVAL_SETUP() \ - ScriptRequest rq(scriptInterface); \ if (val.isPrimitive()) \ return NULL; \ JS::RootedObject obj(rq.cx, &val.toObject()); \ JS::RootedValue prop(rq.cx); #define GET_MSG_PROPERTY(type, name) \ type name; \ { \ if (! JS_GetProperty(rq.cx, obj, #name, &prop)) \ return NULL; \ if (! Script::FromJSVal(rq, prop, name)) \ return NULL; \ } -JS::Value CMessage::ToJSValCached(const ScriptInterface& scriptInterface) const +JS::Value CMessage::ToJSValCached(const ScriptRequest& rq) const { - ScriptRequest rq(scriptInterface); if (!m_Cached) - m_Cached.reset(new JS::PersistentRootedValue(rq.cx, ToJSVal(scriptInterface))); + m_Cached.reset(new JS::PersistentRootedValue(rq.cx, ToJSVal(rq))); return m_Cached->get(); } //////////////////////////////// -JS::Value CMessageTurnStart::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageTurnStart::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); return JS::ObjectValue(*obj); } -CMessage* CMessageTurnStart::FromJSVal(const ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val)) +CMessage* CMessageTurnStart::FromJSVal(const ScriptRequest& UNUSED(rq), JS::HandleValue UNUSED(val)) { return new CMessageTurnStart(); } //////////////////////////////// #define MESSAGE_1(name, t0, a0) \ - JS::Value CMessage##name::ToJSVal(const ScriptInterface& scriptInterface) const \ + JS::Value CMessage##name::ToJSVal(const ScriptRequest& rq) const \ { \ TOJSVAL_SETUP(); \ SET_MSG_PROPERTY(a0); \ return JS::ObjectValue(*obj); \ } \ - CMessage* CMessage##name::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) \ + CMessage* CMessage##name::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) \ { \ FROMJSVAL_SETUP(); \ GET_MSG_PROPERTY(t0, a0); \ return new CMessage##name(a0); \ } MESSAGE_1(Update, fixed, turnLength) MESSAGE_1(Update_MotionFormation, fixed, turnLength) MESSAGE_1(Update_MotionUnit, fixed, turnLength) MESSAGE_1(Update_Final, fixed, turnLength) //////////////////////////////// -JS::Value CMessageInterpolate::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageInterpolate::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(deltaSimTime); SET_MSG_PROPERTY(offset); SET_MSG_PROPERTY(deltaRealTime); return JS::ObjectValue(*obj); } -CMessage* CMessageInterpolate::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageInterpolate::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(float, deltaSimTime); GET_MSG_PROPERTY(float, offset); GET_MSG_PROPERTY(float, deltaRealTime); return new CMessageInterpolate(deltaSimTime, offset, deltaRealTime); } //////////////////////////////// -JS::Value CMessageRenderSubmit::ToJSVal(const ScriptInterface& UNUSED(scriptInterface)) const +JS::Value CMessageRenderSubmit::ToJSVal(const ScriptRequest& UNUSED(rq)) const { LOGWARNING("CMessageRenderSubmit::ToJSVal not implemented"); return JS::UndefinedValue(); } -CMessage* CMessageRenderSubmit::FromJSVal(const ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val)) +CMessage* CMessageRenderSubmit::FromJSVal(const ScriptRequest& UNUSED(rq), JS::HandleValue UNUSED(val)) { LOGWARNING("CMessageRenderSubmit::FromJSVal not implemented"); return NULL; } //////////////////////////////// -JS::Value CMessageProgressiveLoad::ToJSVal(const ScriptInterface& UNUSED(scriptInterface)) const +JS::Value CMessageProgressiveLoad::ToJSVal(const ScriptRequest& UNUSED(rq)) const { LOGWARNING("CMessageProgressiveLoad::ToJSVal not implemented"); return JS::UndefinedValue(); } -CMessage* CMessageProgressiveLoad::FromJSVal(const ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val)) +CMessage* CMessageProgressiveLoad::FromJSVal(const ScriptRequest& UNUSED(rq), JS::HandleValue UNUSED(val)) { LOGWARNING("CMessageProgressiveLoad::FromJSVal not implemented"); return NULL; } //////////////////////////////// -JS::Value CMessageDeserialized::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageDeserialized::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); return JS::ObjectValue(*obj); } -CMessage* CMessageDeserialized::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageDeserialized::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); return new CMessageDeserialized(); } //////////////////////////////// -JS::Value CMessageCreate::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageCreate::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(entity); return JS::ObjectValue(*obj); } -CMessage* CMessageCreate::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageCreate::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(entity_id_t, entity); return new CMessageCreate(entity); } //////////////////////////////// -JS::Value CMessageDestroy::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageDestroy::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(entity); return JS::ObjectValue(*obj); } -CMessage* CMessageDestroy::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageDestroy::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(entity_id_t, entity); return new CMessageDestroy(entity); } //////////////////////////////// -JS::Value CMessageOwnershipChanged::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageOwnershipChanged::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(entity); SET_MSG_PROPERTY(from); SET_MSG_PROPERTY(to); return JS::ObjectValue(*obj); } -CMessage* CMessageOwnershipChanged::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageOwnershipChanged::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(entity_id_t, entity); GET_MSG_PROPERTY(player_id_t, from); GET_MSG_PROPERTY(player_id_t, to); return new CMessageOwnershipChanged(entity, from, to); } //////////////////////////////// -JS::Value CMessagePositionChanged::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessagePositionChanged::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(entity); SET_MSG_PROPERTY(inWorld); SET_MSG_PROPERTY(x); SET_MSG_PROPERTY(z); SET_MSG_PROPERTY(a); return JS::ObjectValue(*obj); } -CMessage* CMessagePositionChanged::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessagePositionChanged::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(entity_id_t, entity); GET_MSG_PROPERTY(bool, inWorld); GET_MSG_PROPERTY(entity_pos_t, x); GET_MSG_PROPERTY(entity_pos_t, z); GET_MSG_PROPERTY(entity_angle_t, a); return new CMessagePositionChanged(entity, inWorld, x, z, a); } //////////////////////////////// -JS::Value CMessageInterpolatedPositionChanged::ToJSVal(const ScriptInterface& UNUSED(scriptInterface)) const +JS::Value CMessageInterpolatedPositionChanged::ToJSVal(const ScriptRequest& UNUSED(rq)) const { LOGWARNING("CMessageInterpolatedPositionChanged::ToJSVal not implemented"); return JS::UndefinedValue(); } -CMessage* CMessageInterpolatedPositionChanged::FromJSVal(const ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val)) +CMessage* CMessageInterpolatedPositionChanged::FromJSVal(const ScriptRequest& UNUSED(rq), JS::HandleValue UNUSED(val)) { LOGWARNING("CMessageInterpolatedPositionChanged::FromJSVal not implemented"); return NULL; } //////////////////////////////// const std::array CMessageMotionUpdate::UpdateTypeStr = { { "likelySuccess", "likelyFailure", "obstructed", "veryObstructed" } }; -JS::Value CMessageMotionUpdate::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageMotionUpdate::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); JS::RootedValue prop(rq.cx); if (!JS_SetProperty(rq.cx, obj, UpdateTypeStr[updateType], JS::TrueHandleValue)) return JS::UndefinedValue(); return JS::ObjectValue(*obj); } -CMessage* CMessageMotionUpdate::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageMotionUpdate::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(std::wstring, updateString); if (updateString == L"likelySuccess") return new CMessageMotionUpdate(CMessageMotionUpdate::LIKELY_SUCCESS); if (updateString == L"likelyFailure") return new CMessageMotionUpdate(CMessageMotionUpdate::LIKELY_FAILURE); if (updateString == L"obstructed") return new CMessageMotionUpdate(CMessageMotionUpdate::OBSTRUCTED); if (updateString == L"veryObstructed") return new CMessageMotionUpdate(CMessageMotionUpdate::VERY_OBSTRUCTED); LOGWARNING("CMessageMotionUpdate::FromJSVal passed wrong updateString"); return NULL; } //////////////////////////////// -JS::Value CMessageTerrainChanged::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageTerrainChanged::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(i0); SET_MSG_PROPERTY(j0); SET_MSG_PROPERTY(i1); SET_MSG_PROPERTY(j1); return JS::ObjectValue(*obj); } -CMessage* CMessageTerrainChanged::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageTerrainChanged::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(int32_t, i0); GET_MSG_PROPERTY(int32_t, j0); GET_MSG_PROPERTY(int32_t, i1); GET_MSG_PROPERTY(int32_t, j1); return new CMessageTerrainChanged(i0, i1, j0, j1); } //////////////////////////////// -JS::Value CMessageVisibilityChanged::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageVisibilityChanged::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(player); SET_MSG_PROPERTY(ent); SET_MSG_PROPERTY(oldVisibility); SET_MSG_PROPERTY(newVisibility); return JS::ObjectValue(*obj); } -CMessage* CMessageVisibilityChanged::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageVisibilityChanged::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(player_id_t, player); GET_MSG_PROPERTY(entity_id_t, ent); GET_MSG_PROPERTY(int, oldVisibility); GET_MSG_PROPERTY(int, newVisibility); return new CMessageVisibilityChanged(player, ent, oldVisibility, newVisibility); } //////////////////////////////// -JS::Value CMessageWaterChanged::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageWaterChanged::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); return JS::ObjectValue(*obj); } -CMessage* CMessageWaterChanged::FromJSVal(const ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val)) +CMessage* CMessageWaterChanged::FromJSVal(const ScriptRequest& UNUSED(rq), JS::HandleValue UNUSED(val)) { return new CMessageWaterChanged(); } //////////////////////////////// -JS::Value CMessageMovementObstructionChanged::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageMovementObstructionChanged::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); return JS::ObjectValue(*obj); } -CMessage* CMessageMovementObstructionChanged::FromJSVal(const ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val)) +CMessage* CMessageMovementObstructionChanged::FromJSVal(const ScriptRequest& UNUSED(rq), JS::HandleValue UNUSED(val)) { return new CMessageMovementObstructionChanged(); } //////////////////////////////// -JS::Value CMessageObstructionMapShapeChanged::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageObstructionMapShapeChanged::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); return JS::ObjectValue(*obj); } -CMessage* CMessageObstructionMapShapeChanged::FromJSVal(const ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val)) +CMessage* CMessageObstructionMapShapeChanged::FromJSVal(const ScriptRequest& UNUSED(rq), JS::HandleValue UNUSED(val)) { return new CMessageObstructionMapShapeChanged(); } //////////////////////////////// -JS::Value CMessageTerritoriesChanged::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageTerritoriesChanged::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); return JS::ObjectValue(*obj); } -CMessage* CMessageTerritoriesChanged::FromJSVal(const ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val)) +CMessage* CMessageTerritoriesChanged::FromJSVal(const ScriptRequest& UNUSED(rq), JS::HandleValue UNUSED(val)) { return new CMessageTerritoriesChanged(); } //////////////////////////////// -JS::Value CMessageRangeUpdate::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageRangeUpdate::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(tag); SET_MSG_PROPERTY(added); SET_MSG_PROPERTY(removed); return JS::ObjectValue(*obj); } -CMessage* CMessageRangeUpdate::FromJSVal(const ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val)) +CMessage* CMessageRangeUpdate::FromJSVal(const ScriptRequest& UNUSED(rq), JS::HandleValue UNUSED(val)) { LOGWARNING("CMessageRangeUpdate::FromJSVal not implemented"); return NULL; } //////////////////////////////// -JS::Value CMessagePathResult::ToJSVal(const ScriptInterface& UNUSED(scriptInterface)) const +JS::Value CMessagePathResult::ToJSVal(const ScriptRequest& UNUSED(rq)) const { LOGWARNING("CMessagePathResult::ToJSVal not implemented"); return JS::UndefinedValue(); } -CMessage* CMessagePathResult::FromJSVal(const ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val)) +CMessage* CMessagePathResult::FromJSVal(const ScriptRequest& UNUSED(rq), JS::HandleValue UNUSED(val)) { LOGWARNING("CMessagePathResult::FromJSVal not implemented"); return NULL; } //////////////////////////////// -JS::Value CMessageValueModification::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageValueModification::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(entities); SET_MSG_PROPERTY(component); SET_MSG_PROPERTY(valueNames); return JS::ObjectValue(*obj); } -CMessage* CMessageValueModification::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageValueModification::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(std::vector, entities); GET_MSG_PROPERTY(std::wstring, component); GET_MSG_PROPERTY(std::vector, valueNames); return new CMessageValueModification(entities, component, valueNames); } //////////////////////////////// -JS::Value CMessageTemplateModification::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageTemplateModification::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(player); SET_MSG_PROPERTY(component); SET_MSG_PROPERTY(valueNames); return JS::ObjectValue(*obj); } -CMessage* CMessageTemplateModification::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageTemplateModification::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(player_id_t, player); GET_MSG_PROPERTY(std::wstring, component); GET_MSG_PROPERTY(std::vector, valueNames); return new CMessageTemplateModification(player, component, valueNames); } //////////////////////////////// -JS::Value CMessageVisionRangeChanged::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageVisionRangeChanged::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(entity); SET_MSG_PROPERTY(oldRange); SET_MSG_PROPERTY(newRange); return JS::ObjectValue(*obj); } -CMessage* CMessageVisionRangeChanged::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageVisionRangeChanged::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(entity_id_t, entity); GET_MSG_PROPERTY(entity_pos_t, oldRange); GET_MSG_PROPERTY(entity_pos_t, newRange); return new CMessageVisionRangeChanged(entity, oldRange, newRange); } -JS::Value CMessageVisionSharingChanged::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageVisionSharingChanged::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(entity); SET_MSG_PROPERTY(player); SET_MSG_PROPERTY(add); return JS::ObjectValue(*obj); } -CMessage* CMessageVisionSharingChanged::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageVisionSharingChanged::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(entity_id_t, entity); GET_MSG_PROPERTY(player_id_t, player); GET_MSG_PROPERTY(bool, add); return new CMessageVisionSharingChanged(entity, player, add); } //////////////////////////////// -JS::Value CMessageMinimapPing::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageMinimapPing::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); return JS::ObjectValue(*obj); } -CMessage* CMessageMinimapPing::FromJSVal(const ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val)) +CMessage* CMessageMinimapPing::FromJSVal(const ScriptRequest& UNUSED(rq), JS::HandleValue UNUSED(val)) { return new CMessageMinimapPing(); } //////////////////////////////// -JS::Value CMessageCinemaPathEnded::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageCinemaPathEnded::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(name); return JS::ObjectValue(*obj); } -CMessage* CMessageCinemaPathEnded::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageCinemaPathEnded::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(CStrW, name); return new CMessageCinemaPathEnded(name); } //////////////////////////////// -JS::Value CMessageCinemaQueueEnded::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessageCinemaQueueEnded::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); return JS::ObjectValue(*obj); } -CMessage* CMessageCinemaQueueEnded::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessageCinemaQueueEnded::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); return new CMessageCinemaQueueEnded(); } //////////////////////////////////////////////////////////////// -JS::Value CMessagePlayerColorChanged::ToJSVal(const ScriptInterface& scriptInterface) const +JS::Value CMessagePlayerColorChanged::ToJSVal(const ScriptRequest& rq) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(player); return JS::ObjectValue(*obj); } -CMessage* CMessagePlayerColorChanged::FromJSVal(const ScriptInterface& scriptInterface, JS::HandleValue val) +CMessage* CMessagePlayerColorChanged::FromJSVal(const ScriptRequest& rq, JS::HandleValue val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(player_id_t, player); return new CMessagePlayerColorChanged(player); } //////////////////////////////////////////////////////////////// -CMessage* CMessageFromJSVal(int mtid, const ScriptInterface& scriptingInterface, JS::HandleValue val) +CMessage* CMessageFromJSVal(int mtid, const ScriptRequest& rq, JS::HandleValue val) { switch (mtid) { -#define MESSAGE(name) case MT_##name: return CMessage##name::FromJSVal(scriptingInterface, val); +#define MESSAGE(name) case MT_##name: return CMessage##name::FromJSVal(rq, val); #define INTERFACE(name) #define COMPONENT(name) #include "simulation2/TypeList.h" #undef COMPONENT #undef INTERFACE #undef MESSAGE } return NULL; } Index: ps/trunk/source/simulation2/scripting/ScriptComponent.cpp =================================================================== --- ps/trunk/source/simulation2/scripting/ScriptComponent.cpp (revision 27727) +++ ps/trunk/source/simulation2/scripting/ScriptComponent.cpp (revision 27728) @@ -1,89 +1,89 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ScriptComponent.h" #include "scriptinterface/FunctionWrapper.h" #include "scriptinterface/JSON.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/Object.h" #include "simulation2/serialization/ISerializer.h" #include "simulation2/serialization/IDeserializer.h" CComponentTypeScript::CComponentTypeScript(const ScriptInterface& scriptInterface, JS::HandleValue instance) : m_ScriptInterface(scriptInterface) { m_Instance.init(ScriptRequest(m_ScriptInterface).cx, instance); } void CComponentTypeScript::Init(const CParamNode& paramNode, entity_id_t ent) { 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"); } void CComponentTypeScript::Deinit() { ScriptRequest rq(m_ScriptInterface); ScriptFunction::CallVoid(rq, m_Instance, "Deinit"); } void CComponentTypeScript::HandleMessage(const CMessage& msg, bool global) { ScriptRequest rq(m_ScriptInterface); const char* name = global ? msg.GetScriptGlobalHandlerName() : msg.GetScriptHandlerName(); - JS::RootedValue msgVal(rq.cx, msg.ToJSValCached(m_ScriptInterface)); + JS::RootedValue msgVal(rq.cx, msg.ToJSValCached(rq)); if (!ScriptFunction::CallVoid(rq, m_Instance, name, msgVal)) LOGERROR("Script message handler %s failed", name); } void CComponentTypeScript::Serialize(ISerializer& serialize) { ScriptRequest rq(m_ScriptInterface); try { serialize.ScriptVal("comp", &m_Instance); } catch(PSERROR_Serialize& err) { int ent = INVALID_ENTITY; Script::GetProperty(rq, m_Instance, "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)); // Rethrow now that we added more details throw; } } void CComponentTypeScript::Deserialize(const CParamNode& paramNode, IDeserializer& deserialize, entity_id_t ent) { ScriptRequest rq(m_ScriptInterface); Script::SetProperty(rq, m_Instance, "entity", (int)ent, true, false); Script::SetProperty(rq, m_Instance, "template", paramNode, true, false); deserialize.ScriptObjectAssign("comp", m_Instance); } Index: ps/trunk/source/simulation2/system/ComponentManager.cpp =================================================================== --- ps/trunk/source/simulation2/system/ComponentManager.cpp (revision 27727) +++ ps/trunk/source/simulation2/system/ComponentManager.cpp (revision 27728) @@ -1,1163 +1,1164 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ComponentManager.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Profile.h" #include "ps/scripting/JSInterface_VFS.h" #include "scriptinterface/FunctionWrapper.h" #include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/MessageTypes.h" #include "simulation2/system/DynamicSubscription.h" #include "simulation2/system/IComponent.h" #include "simulation2/system/ParamNode.h" #include "simulation2/system/SimContext.h" #include /** * Used for script-only message types. */ class CMessageScripted final : public CMessage { public: virtual int GetType() const { return mtid; } virtual const char* GetScriptHandlerName() const { return handlerName.c_str(); } virtual const char* GetScriptGlobalHandlerName() const { return globalHandlerName.c_str(); } - virtual JS::Value ToJSVal(const ScriptInterface& UNUSED(scriptInterface)) const { return msg.get(); } + virtual JS::Value ToJSVal(const ScriptRequest& UNUSED(rq)) const { return msg.get(); } - CMessageScripted(const ScriptInterface& scriptInterface, int mtid, const std::string& name, JS::HandleValue msg) : - mtid(mtid), handlerName("On" + name), globalHandlerName("OnGlobal" + name), msg(scriptInterface.GetGeneralJSContext(), msg) + CMessageScripted(const ScriptRequest& rq, int mtid, const std::string& name, JS::HandleValue msg) : + mtid(mtid), handlerName("On" + name), globalHandlerName("OnGlobal" + name), msg(rq.cx, msg) { } int mtid; std::string handlerName; std::string globalHandlerName; JS::PersistentRootedValue msg; }; CComponentManager::CComponentManager(CSimContext& context, std::shared_ptr cx, bool skipScriptFunctions) : m_NextScriptComponentTypeId(CID__LastNative), m_ScriptInterface("Engine", "Simulation", cx), m_SimContext(context), m_CurrentlyHotloading(false) { context.SetComponentManager(this); m_ScriptInterface.SetCallbackData(static_cast (this)); m_ScriptInterface.ReplaceNondeterministicRNG(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) { JSI_VFS::RegisterScriptFunctions_ReadOnlySimulation(m_ScriptInterface); ScriptRequest rq(m_ScriptInterface); constexpr ScriptFunction::ObjectGetter Getter = &ScriptInterface::ObjectFromCBData; ScriptFunction::Register<&CComponentManager::Script_RegisterComponentType, Getter>(rq, "RegisterComponentType"); ScriptFunction::Register<&CComponentManager::Script_RegisterSystemComponentType, Getter>(rq, "RegisterSystemComponentType"); ScriptFunction::Register<&CComponentManager::Script_ReRegisterComponentType, Getter>(rq, "ReRegisterComponentType"); ScriptFunction::Register<&CComponentManager::Script_RegisterInterface, Getter>(rq, "RegisterInterface"); ScriptFunction::Register<&CComponentManager::Script_RegisterMessageType, Getter>(rq, "RegisterMessageType"); ScriptFunction::Register<&CComponentManager::Script_RegisterGlobal, Getter>(rq, "RegisterGlobal"); ScriptFunction::Register<&CComponentManager::Script_GetEntitiesWithInterface, Getter>(rq, "GetEntitiesWithInterface"); ScriptFunction::Register<&CComponentManager::Script_GetComponentsWithInterface, Getter>(rq, "GetComponentsWithInterface"); ScriptFunction::Register<&CComponentManager::Script_PostMessage, Getter>(rq, "PostMessage"); ScriptFunction::Register<&CComponentManager::Script_BroadcastMessage, Getter>(rq, "BroadcastMessage"); ScriptFunction::Register<&CComponentManager::Script_AddEntity, Getter>(rq, "AddEntity"); ScriptFunction::Register<&CComponentManager::Script_AddLocalEntity, Getter>(rq, "AddLocalEntity"); ScriptFunction::Register<&CComponentManager::QueryInterface, Getter>(rq, "QueryInterface"); ScriptFunction::Register<&CComponentManager::DestroyComponentsSoon, Getter>(rq, "DestroyEntity"); ScriptFunction::Register<&CComponentManager::FlushDestroyedComponents, Getter>(rq, "FlushDestroyedEntities"); ScriptFunction::Register<&CComponentManager::Script_GetTemplate, Getter>(rq, "GetTemplate"); } // Globalscripts may use VFS script functions m_ScriptInterface.LoadGlobalScripts(); // Define MT_*, IID_* as script globals, and store their names #define MESSAGE(name) m_ScriptInterface.SetGlobal("MT_" #name, (int)MT_##name); #define INTERFACE(name) \ m_ScriptInterface.SetGlobal("IID_" #name, (int)IID_##name); \ m_InterfaceIdsByName[#name] = IID_##name; #define COMPONENT(name) #include "simulation2/TypeList.h" #undef MESSAGE #undef INTERFACE #undef COMPONENT m_ScriptInterface.SetGlobal("INVALID_ENTITY", (int)INVALID_ENTITY); m_ScriptInterface.SetGlobal("INVALID_PLAYER", (int)INVALID_PLAYER); m_ScriptInterface.SetGlobal("SYSTEM_ENTITY", (int)SYSTEM_ENTITY); m_ComponentsByInterface.resize(IID__LastNative); ResetState(); } CComponentManager::~CComponentManager() { ResetState(); } void CComponentManager::LoadComponentTypes() { #define MESSAGE(name) \ RegisterMessageType(MT_##name, #name); #define INTERFACE(name) \ extern void RegisterComponentInterface_##name(ScriptInterface&); \ RegisterComponentInterface_##name(m_ScriptInterface); #define COMPONENT(name) \ extern void RegisterComponentType_##name(CComponentManager&); \ m_CurrentComponent = CID_##name; \ RegisterComponentType_##name(*this); #include "simulation2/TypeList.h" m_CurrentComponent = CID__Invalid; #undef MESSAGE #undef INTERFACE #undef COMPONENT } bool CComponentManager::LoadScript(const VfsPath& filename, bool hotload) { m_CurrentlyHotloading = hotload; CVFSFile file; PSRETURN loadOk = file.Load(g_VFS, filename); if (loadOk != PSRETURN_OK) // VFS will log the failed file and the reason return false; std::string content = file.DecodeUTF8(); // assume it's UTF-8 bool ok = m_ScriptInterface.LoadScript(filename, content); m_CurrentlyHotloading = false; return ok; } void CComponentManager::Script_RegisterComponentType_Common(int iid, const std::string& cname, JS::HandleValue ctor, bool reRegister, bool systemComponent) { ScriptRequest rq(m_ScriptInterface); // Find the C++ component that wraps the interface int cidWrapper = GetScriptWrapper(iid); if (cidWrapper == CID__Invalid) { ScriptException::Raise(rq, "Invalid interface id"); return; } const ComponentType& ctWrapper = m_ComponentTypesById[cidWrapper]; bool mustReloadComponents = false; // for hotloading ComponentTypeId cid = LookupCID(cname); if (cid == CID__Invalid) { if (reRegister) { ScriptException::Raise(rq, "ReRegistering component type that was not registered before '%s'", cname.c_str()); return; } // Allocate a new cid number cid = m_NextScriptComponentTypeId++; m_ComponentTypeIdsByName[cname] = cid; if (systemComponent) MarkScriptedComponentForSystemEntity(cid); } else { // Component type is already loaded, so do hotloading: if (!m_CurrentlyHotloading && !reRegister) { ScriptException::Raise(rq, "Registering component type with already-registered name '%s'", cname.c_str()); return; } const ComponentType& ctPrevious = m_ComponentTypesById[cid]; // We can only replace scripted component types, not native ones if (ctPrevious.type != CT_Script) { ScriptException::Raise(rq, "Loading script component type with same name '%s' as native component", cname.c_str()); return; } // We don't support changing the IID of a component type (it would require fiddling // around with m_ComponentsByInterface and being careful to guarantee uniqueness per entity) if (ctPrevious.iid != iid) { // ...though it only matters if any components exist with this type if (!m_ComponentsByTypeId[cid].empty()) { ScriptException::Raise(rq, "Hotloading script component type mustn't change interface ID"); return; } } // Remove the old component type's message subscriptions std::map >::iterator it; for (it = m_LocalMessageSubscriptions.begin(); it != m_LocalMessageSubscriptions.end(); ++it) { std::vector& types = it->second; std::vector::iterator ctit = find(types.begin(), types.end(), cid); if (ctit != types.end()) types.erase(ctit); } for (it = m_GlobalMessageSubscriptions.begin(); it != m_GlobalMessageSubscriptions.end(); ++it) { std::vector& types = it->second; std::vector::iterator ctit = find(types.begin(), types.end(), cid); if (ctit != types.end()) types.erase(ctit); } mustReloadComponents = true; } JS::RootedValue protoVal(rq.cx); if (!Script::GetProperty(rq, ctor, "prototype", &protoVal)) { ScriptException::Raise(rq, "Failed to get property 'prototype'"); return; } if (!protoVal.isObject()) { ScriptException::Raise(rq, "Component has no constructor"); return; } std::string schema = ""; if (Script::HasProperty(rq, protoVal, "Schema")) Script::GetProperty(rq, protoVal, "Schema", schema); // Construct a new ComponentType, using the wrapper's alloc functions ComponentType ct{ CT_Script, iid, ctWrapper.alloc, ctWrapper.dealloc, cname, schema, std::make_unique(rq.cx, ctor) }; m_ComponentTypesById[cid] = std::move(ct); m_CurrentComponent = cid; // needed by Subscribe // Find all the ctor prototype's On* methods, and subscribe to the appropriate messages: std::vector methods; if (!Script::EnumeratePropertyNames(rq, protoVal, false, methods)) { ScriptException::Raise(rq, "Failed to enumerate component properties."); return; } for (const std::string& method : methods) { if (std::string_view{method}.substr(0, 2) != "On") continue; std::string_view name{std::string_view{method}.substr(2)}; // strip the "On" prefix // Handle "OnGlobalFoo" functions specially bool isGlobal = false; if (std::string_view{name}.substr(0, 6) == "Global") { isGlobal = true; name.remove_prefix(6); } auto mit = m_MessageTypeIdsByName.find(std::string{name}); if (mit == m_MessageTypeIdsByName.end()) { ScriptException::Raise(rq, "Registered component has unrecognized '%s' message handler method", method.c_str()); return; } if (isGlobal) SubscribeGloballyToMessageType(mit->second); else SubscribeToMessageType(mit->second); } m_CurrentComponent = CID__Invalid; if (mustReloadComponents) { // For every script component with this cid, we need to switch its // prototype from the old constructor's prototype property to the new one's const std::map& comps = m_ComponentsByTypeId[cid]; std::map::const_iterator eit = comps.begin(); for (; eit != comps.end(); ++eit) { JS::RootedValue instance(rq.cx, eit->second->GetJSInstance()); if (!instance.isNull()) m_ScriptInterface.SetPrototype(instance, protoVal); } } } void CComponentManager::Script_RegisterComponentType(int iid, const std::string& cname, JS::HandleValue ctor) { Script_RegisterComponentType_Common(iid, cname, ctor, false, false); m_ScriptInterface.SetGlobal(cname.c_str(), ctor, m_CurrentlyHotloading); } void CComponentManager::Script_RegisterSystemComponentType(int iid, const std::string& cname, JS::HandleValue ctor) { Script_RegisterComponentType_Common(iid, cname, ctor, false, true); m_ScriptInterface.SetGlobal(cname.c_str(), ctor, m_CurrentlyHotloading); } void CComponentManager::Script_ReRegisterComponentType(int iid, const std::string& cname, JS::HandleValue ctor) { Script_RegisterComponentType_Common(iid, cname, ctor, true, false); } void CComponentManager::Script_RegisterInterface(const std::string& name) { std::map::iterator it = m_InterfaceIdsByName.find(name); if (it != m_InterfaceIdsByName.end()) { // Redefinitions are fine (and just get ignored) when hotloading; otherwise // they're probably unintentional and should be reported if (!m_CurrentlyHotloading) { ScriptRequest rq(m_ScriptInterface); ScriptException::Raise(rq, "Registering interface with already-registered name '%s'", name.c_str()); } return; } // IIDs start at 1, so size+1 is the next unused one size_t id = m_InterfaceIdsByName.size() + 1; m_InterfaceIdsByName[name] = (InterfaceId)id; m_ComponentsByInterface.resize(id+1); // add one so we can index by InterfaceId m_ScriptInterface.SetGlobal(("IID_" + name).c_str(), (int)id); } void CComponentManager::Script_RegisterMessageType(const std::string& name) { std::map::iterator it = m_MessageTypeIdsByName.find(name); if (it != m_MessageTypeIdsByName.end()) { // Redefinitions are fine (and just get ignored) when hotloading; otherwise // they're probably unintentional and should be reported if (!m_CurrentlyHotloading) { ScriptRequest rq(m_ScriptInterface); ScriptException::Raise(rq, "Registering message type with already-registered name '%s'", name.c_str()); } return; } // MTIDs start at 1, so size+1 is the next unused one size_t id = m_MessageTypeIdsByName.size() + 1; RegisterMessageType((MessageTypeId)id, name.c_str()); m_ScriptInterface.SetGlobal(("MT_" + name).c_str(), (int)id); } void CComponentManager::Script_RegisterGlobal(const std::string& name, JS::HandleValue value) { m_ScriptInterface.SetGlobal(name.c_str(), value, m_CurrentlyHotloading); } const CParamNode& CComponentManager::Script_GetTemplate(const std::string& templateName) { static CParamNode nullNode(false); ICmpTemplateManager* cmpTemplateManager = static_cast (QueryInterface(SYSTEM_ENTITY, IID_TemplateManager)); if (!cmpTemplateManager) { LOGERROR("Template manager is not loaded"); return nullNode; } const CParamNode* tmpl = cmpTemplateManager->GetTemplate(templateName); if (!tmpl) return nullNode; return *tmpl; } std::vector CComponentManager::Script_GetEntitiesWithInterface(int iid) { std::vector ret; const InterfaceListUnordered& ents = GetEntitiesWithInterfaceUnordered(iid); for (InterfaceListUnordered::const_iterator it = ents.begin(); it != ents.end(); ++it) if (!ENTITY_IS_LOCAL(it->first)) ret.push_back(it->first); std::sort(ret.begin(), ret.end()); return ret; } std::vector CComponentManager::Script_GetComponentsWithInterface(int iid) { std::vector ret; InterfaceList ents = GetEntitiesWithInterface(iid); for (InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) ret.push_back(it->second); // TODO: maybe we should exclude local entities return ret; } CMessage* CComponentManager::ConstructMessage(int mtid, JS::HandleValue data) { if (mtid == MT__Invalid || mtid > (int)m_MessageTypeIdsByName.size()) // (IDs start at 1 so use '>' here) LOGERROR("PostMessage with invalid message type ID '%d'", mtid); + ScriptRequest rq(m_ScriptInterface); if (mtid < MT__LastNative) { - return CMessageFromJSVal(mtid, m_ScriptInterface, data); + return CMessageFromJSVal(mtid, rq, data); } else { - return new CMessageScripted(m_ScriptInterface, mtid, m_MessageTypeNamesById[mtid], data); + return new CMessageScripted(rq, mtid, m_MessageTypeNamesById[mtid], data); } } void CComponentManager::Script_PostMessage(int ent, int mtid, JS::HandleValue data) { CMessage* msg = ConstructMessage(mtid, data); if (!msg) return; // error PostMessage(ent, *msg); delete msg; } void CComponentManager::Script_BroadcastMessage(int mtid, JS::HandleValue data) { CMessage* msg = ConstructMessage(mtid, data); if (!msg) return; // error BroadcastMessage(*msg); delete msg; } int CComponentManager::Script_AddEntity(const std::wstring& templateName) { // TODO: should validate the string to make sure it doesn't contain scary characters // that will let it access non-component-template files return AddEntity(templateName, AllocateNewEntity()); } int CComponentManager::Script_AddLocalEntity(const std::wstring& templateName) { // TODO: should validate the string to make sure it doesn't contain scary characters // that will let it access non-component-template files return AddEntity(templateName, AllocateNewLocalEntity()); } void CComponentManager::ResetState() { // Delete all dynamic message subscriptions m_DynamicMessageSubscriptionsNonsync.clear(); m_DynamicMessageSubscriptionsNonsyncByComponent.clear(); // Delete all IComponents in reverse order of creation. std::map >::reverse_iterator iit = m_ComponentsByTypeId.rbegin(); for (; iit != m_ComponentsByTypeId.rend(); ++iit) { std::map::iterator eit = iit->second.begin(); for (; eit != iit->second.end(); ++eit) { eit->second->Deinit(); m_ComponentTypesById[iit->first].dealloc(eit->second); } } std::vector >::iterator ifcit = m_ComponentsByInterface.begin(); for (; ifcit != m_ComponentsByInterface.end(); ++ifcit) ifcit->clear(); m_ComponentsByTypeId.clear(); // Delete all SEntityComponentCaches std::unordered_map::iterator ccit = m_ComponentCaches.begin(); for (; ccit != m_ComponentCaches.end(); ++ccit) free(ccit->second); m_ComponentCaches.clear(); m_SystemEntity = CEntityHandle(); m_DestructionQueue.clear(); // Reset IDs m_NextEntityId = SYSTEM_ENTITY + 1; m_NextLocalEntityId = FIRST_LOCAL_ENTITY; } void CComponentManager::SetRNGSeed(u32 seed) { m_RNG.seed(seed); } void CComponentManager::RegisterComponentType(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name, const std::string& schema) { ComponentType c{ CT_Native, iid, alloc, dealloc, name, schema, std::unique_ptr() }; m_ComponentTypesById.insert(std::make_pair(cid, std::move(c))); m_ComponentTypeIdsByName[name] = cid; } void CComponentManager::RegisterComponentTypeScriptWrapper(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name, const std::string& schema) { ComponentType c{ CT_ScriptWrapper, iid, alloc, dealloc, name, schema, std::unique_ptr() }; m_ComponentTypesById.insert(std::make_pair(cid, std::move(c))); m_ComponentTypeIdsByName[name] = cid; // TODO: merge with RegisterComponentType } void CComponentManager::MarkScriptedComponentForSystemEntity(CComponentManager::ComponentTypeId cid) { m_ScriptedSystemComponents.push_back(cid); } void CComponentManager::RegisterMessageType(MessageTypeId mtid, const char* name) { m_MessageTypeIdsByName[name] = mtid; m_MessageTypeNamesById[mtid] = name; } void CComponentManager::SubscribeToMessageType(MessageTypeId mtid) { // TODO: verify mtid ENSURE(m_CurrentComponent != CID__Invalid); std::vector& types = m_LocalMessageSubscriptions[mtid]; types.push_back(m_CurrentComponent); std::sort(types.begin(), types.end()); // TODO: just sort once at the end of LoadComponents } void CComponentManager::SubscribeGloballyToMessageType(MessageTypeId mtid) { // TODO: verify mtid ENSURE(m_CurrentComponent != CID__Invalid); std::vector& types = m_GlobalMessageSubscriptions[mtid]; types.push_back(m_CurrentComponent); std::sort(types.begin(), types.end()); // TODO: just sort once at the end of LoadComponents } void CComponentManager::FlattenDynamicSubscriptions() { std::map::iterator it; for (it = m_DynamicMessageSubscriptionsNonsync.begin(); it != m_DynamicMessageSubscriptionsNonsync.end(); ++it) { it->second.Flatten(); } } void CComponentManager::DynamicSubscriptionNonsync(MessageTypeId mtid, IComponent* component, bool enable) { if (enable) { bool newlyInserted = m_DynamicMessageSubscriptionsNonsyncByComponent[component].insert(mtid).second; if (newlyInserted) m_DynamicMessageSubscriptionsNonsync[mtid].Add(component); } else { size_t numRemoved = m_DynamicMessageSubscriptionsNonsyncByComponent[component].erase(mtid); if (numRemoved) m_DynamicMessageSubscriptionsNonsync[mtid].Remove(component); } } void CComponentManager::RemoveComponentDynamicSubscriptions(IComponent* component) { std::map >::iterator it = m_DynamicMessageSubscriptionsNonsyncByComponent.find(component); if (it == m_DynamicMessageSubscriptionsNonsyncByComponent.end()) return; std::set::iterator mtit; for (mtit = it->second.begin(); mtit != it->second.end(); ++mtit) { m_DynamicMessageSubscriptionsNonsync[*mtit].Remove(component); // Need to flatten the subscription lists immediately to avoid dangling IComponent* references m_DynamicMessageSubscriptionsNonsync[*mtit].Flatten(); } m_DynamicMessageSubscriptionsNonsyncByComponent.erase(it); } CComponentManager::ComponentTypeId CComponentManager::LookupCID(const std::string& cname) const { std::map::const_iterator it = m_ComponentTypeIdsByName.find(cname); if (it == m_ComponentTypeIdsByName.end()) return CID__Invalid; return it->second; } std::string CComponentManager::LookupComponentTypeName(ComponentTypeId cid) const { std::map::const_iterator it = m_ComponentTypesById.find(cid); if (it == m_ComponentTypesById.end()) return ""; return it->second.name; } CComponentManager::ComponentTypeId CComponentManager::GetScriptWrapper(InterfaceId iid) { if (iid >= IID__LastNative && iid <= (int)m_InterfaceIdsByName.size()) // use <= since IDs start at 1 return CID_UnknownScript; std::map::const_iterator it = m_ComponentTypesById.begin(); for (; it != m_ComponentTypesById.end(); ++it) if (it->second.iid == iid && it->second.type == CT_ScriptWrapper) return it->first; std::map::const_iterator iiit = m_InterfaceIdsByName.begin(); for (; iiit != m_InterfaceIdsByName.end(); ++iiit) if (iiit->second == iid) { LOGERROR("No script wrapper found for interface id %d '%s'", iid, iiit->first.c_str()); return CID__Invalid; } LOGERROR("No script wrapper found for interface id %d", iid); return CID__Invalid; } entity_id_t CComponentManager::AllocateNewEntity() { entity_id_t id = m_NextEntityId++; // TODO: check for overflow return id; } entity_id_t CComponentManager::AllocateNewLocalEntity() { entity_id_t id = m_NextLocalEntityId++; // TODO: check for overflow return id; } entity_id_t CComponentManager::AllocateNewEntity(entity_id_t preferredId) { // TODO: ensure this ID hasn't been allocated before // (this might occur with broken map files) // Trying to actually add two entities with the same id will fail in AddEntitiy entity_id_t id = preferredId; // Ensure this ID won't be allocated again if (id >= m_NextEntityId) m_NextEntityId = id+1; // TODO: check for overflow return id; } bool CComponentManager::AddComponent(CEntityHandle ent, ComponentTypeId cid, const CParamNode& paramNode) { IComponent* component = ConstructComponent(ent, cid); if (!component) return false; component->Init(paramNode); return true; } void CComponentManager::AddSystemComponents(bool skipScriptedComponents, bool skipAI) { CParamNode noParam; AddComponent(m_SystemEntity, CID_TemplateManager, noParam); AddComponent(m_SystemEntity, CID_CinemaManager, noParam); AddComponent(m_SystemEntity, CID_CommandQueue, noParam); AddComponent(m_SystemEntity, CID_ObstructionManager, noParam); AddComponent(m_SystemEntity, CID_ParticleManager, noParam); AddComponent(m_SystemEntity, CID_Pathfinder, noParam); AddComponent(m_SystemEntity, CID_ProjectileManager, noParam); AddComponent(m_SystemEntity, CID_RangeManager, noParam); AddComponent(m_SystemEntity, CID_SoundManager, noParam); AddComponent(m_SystemEntity, CID_Terrain, noParam); AddComponent(m_SystemEntity, CID_TerritoryManager, noParam); AddComponent(m_SystemEntity, CID_UnitMotionManager, noParam); AddComponent(m_SystemEntity, CID_UnitRenderer, noParam); AddComponent(m_SystemEntity, CID_WaterManager, noParam); // Add scripted system components: if (!skipScriptedComponents) { for (uint32_t i = 0; i < m_ScriptedSystemComponents.size(); ++i) AddComponent(m_SystemEntity, m_ScriptedSystemComponents[i], noParam); if (!skipAI) AddComponent(m_SystemEntity, CID_AIManager, noParam); } } IComponent* CComponentManager::ConstructComponent(CEntityHandle ent, ComponentTypeId cid) { ScriptRequest rq(m_ScriptInterface); std::map::const_iterator it = m_ComponentTypesById.find(cid); if (it == m_ComponentTypesById.end()) { LOGERROR("Invalid component id %d", cid); return NULL; } const ComponentType& ct = it->second; ENSURE((size_t)ct.iid < m_ComponentsByInterface.size()); std::unordered_map& emap1 = m_ComponentsByInterface[ct.iid]; if (emap1.find(ent.GetId()) != emap1.end()) { LOGERROR("Multiple components for interface %d", ct.iid); return NULL; } std::map& emap2 = m_ComponentsByTypeId[cid]; // If this is a scripted component, construct the appropriate JS object first JS::RootedValue obj(rq.cx); if (ct.type == CT_Script) { m_ScriptInterface.CallConstructor(*ct.ctor, JS::HandleValueArray::empty(), &obj); if (obj.isNull()) { LOGERROR("Script component constructor failed"); return NULL; } } // Construct the new component // NB: The unit motion manager relies on components not moving in memory once constructed. IComponent* component = ct.alloc(m_ScriptInterface, obj); ENSURE(component); component->SetEntityHandle(ent); component->SetSimContext(m_SimContext); // Store a reference to the new component emap1.insert(std::make_pair(ent.GetId(), component)); emap2.insert(std::make_pair(ent.GetId(), component)); // TODO: We need to more careful about this - if an entity is constructed by a component // while we're iterating over all components, this will invalidate the iterators and everything // will break. // We probably need some kind of delayed addition, so they get pushed onto a queue and then // inserted into the world later on. (Be careful about immediation deletion in that case, too.) SEntityComponentCache* cache = ent.GetComponentCache(); ENSURE(cache != NULL && ct.iid < (int)cache->numInterfaces && cache->interfaces[ct.iid] == NULL); cache->interfaces[ct.iid] = component; return component; } void CComponentManager::AddMockComponent(CEntityHandle ent, InterfaceId iid, IComponent& component) { // Just add it into the by-interface map, not the by-component-type map, // so it won't be considered for messages or deletion etc std::unordered_map& emap1 = m_ComponentsByInterface.at(iid); if (emap1.find(ent.GetId()) != emap1.end()) debug_warn(L"Multiple components for interface"); emap1.insert(std::make_pair(ent.GetId(), &component)); SEntityComponentCache* cache = ent.GetComponentCache(); ENSURE(cache != NULL && iid < (int)cache->numInterfaces && cache->interfaces[iid] == NULL); cache->interfaces[iid] = &component; } CEntityHandle CComponentManager::AllocateEntityHandle(entity_id_t ent) { ENSURE(!EntityExists(ent)); // Interface IDs start at 1, and SEntityComponentCache is defined with a 1-sized array, // so we need space for an extra m_InterfaceIdsByName.size() items SEntityComponentCache* cache = (SEntityComponentCache*)calloc(1, sizeof(SEntityComponentCache) + sizeof(IComponent*) * m_InterfaceIdsByName.size()); ENSURE(cache != NULL); cache->numInterfaces = m_InterfaceIdsByName.size() + 1; m_ComponentCaches[ent] = cache; return CEntityHandle(ent, cache); } CEntityHandle CComponentManager::LookupEntityHandle(entity_id_t ent, bool allowCreate) { std::unordered_map::iterator it; it = m_ComponentCaches.find(ent); if (it == m_ComponentCaches.end()) { if (allowCreate) return AllocateEntityHandle(ent); else return CEntityHandle(ent, NULL); } else return CEntityHandle(ent, it->second); } void CComponentManager::InitSystemEntity() { ENSURE(m_SystemEntity.GetId() == INVALID_ENTITY); m_SystemEntity = AllocateEntityHandle(SYSTEM_ENTITY); m_SimContext.SetSystemEntity(m_SystemEntity); } entity_id_t CComponentManager::AddEntity(const std::wstring& templateName, entity_id_t ent) { ICmpTemplateManager *cmpTemplateManager = static_cast (QueryInterface(SYSTEM_ENTITY, IID_TemplateManager)); if (!cmpTemplateManager) { debug_warn(L"No ICmpTemplateManager loaded"); return INVALID_ENTITY; } const CParamNode* tmpl = cmpTemplateManager->LoadTemplate(ent, utf8_from_wstring(templateName)); if (!tmpl) return INVALID_ENTITY; // LoadTemplate will have reported the error // This also ensures that ent does not exist CEntityHandle handle = AllocateEntityHandle(ent); // Construct a component for each child of the root element const CParamNode::ChildrenMap& tmplChilds = tmpl->GetChildren(); for (CParamNode::ChildrenMap::const_iterator it = tmplChilds.begin(); it != tmplChilds.end(); ++it) { // Ignore attributes on the root element if (it->first.length() && it->first[0] == '@') continue; CComponentManager::ComponentTypeId cid = LookupCID(it->first); if (cid == CID__Invalid) { LOGERROR("Unrecognized component type name '%s' in entity template '%s'", it->first, utf8_from_wstring(templateName)); return INVALID_ENTITY; } if (!AddComponent(handle, cid, it->second)) { LOGERROR("Failed to construct component type name '%s' in entity template '%s'", it->first, utf8_from_wstring(templateName)); return INVALID_ENTITY; } // TODO: maybe we should delete already-constructed components if one of them fails? } CMessageCreate msg(ent); PostMessage(ent, msg); return ent; } bool CComponentManager::EntityExists(entity_id_t ent) const { return m_ComponentCaches.find(ent) != m_ComponentCaches.end(); } void CComponentManager::DestroyComponentsSoon(entity_id_t ent) { m_DestructionQueue.push_back(ent); } void CComponentManager::FlushDestroyedComponents() { PROFILE2("Flush Destroyed Components"); while (!m_DestructionQueue.empty()) { // Make a copy of the destruction queue, so that the iterators won't be invalidated if the // CMessageDestroy handlers try to destroy more entities themselves std::vector queue; queue.swap(m_DestructionQueue); for (std::vector::iterator it = queue.begin(); it != queue.end(); ++it) { entity_id_t ent = *it; // Do nothing if invalid, destroyed, etc. if (!EntityExists(ent)) continue; CEntityHandle handle = LookupEntityHandle(ent); CMessageDestroy msg(ent); PostMessage(ent, msg); // Flatten all the dynamic subscriptions to ensure there are no dangling // references in the 'removed' lists to components we're going to delete // Some components may have dynamically unsubscribed following the Destroy message FlattenDynamicSubscriptions(); // Destroy the components, and remove from m_ComponentsByTypeId: std::map >::iterator iit = m_ComponentsByTypeId.begin(); for (; iit != m_ComponentsByTypeId.end(); ++iit) { std::map::iterator eit = iit->second.find(ent); if (eit != iit->second.end()) { eit->second->Deinit(); RemoveComponentDynamicSubscriptions(eit->second); m_ComponentTypesById[iit->first].dealloc(eit->second); iit->second.erase(ent); handle.GetComponentCache()->interfaces[m_ComponentTypesById[iit->first].iid] = NULL; } } free(handle.GetComponentCache()); m_ComponentCaches.erase(ent); // Remove from m_ComponentsByInterface std::vector >::iterator ifcit = m_ComponentsByInterface.begin(); for (; ifcit != m_ComponentsByInterface.end(); ++ifcit) { ifcit->erase(ent); } } } } IComponent* CComponentManager::QueryInterface(entity_id_t ent, InterfaceId iid) const { if ((size_t)iid >= m_ComponentsByInterface.size()) { // Invalid iid return NULL; } std::unordered_map::const_iterator eit = m_ComponentsByInterface[iid].find(ent); if (eit == m_ComponentsByInterface[iid].end()) { // This entity doesn't implement this interface return NULL; } return eit->second; } CComponentManager::InterfaceList CComponentManager::GetEntitiesWithInterface(InterfaceId iid) const { std::vector > ret; if ((size_t)iid >= m_ComponentsByInterface.size()) { // Invalid iid return ret; } ret.reserve(m_ComponentsByInterface[iid].size()); std::unordered_map::const_iterator it = m_ComponentsByInterface[iid].begin(); for (; it != m_ComponentsByInterface[iid].end(); ++it) ret.push_back(*it); std::sort(ret.begin(), ret.end()); // lexicographic pair comparison means this'll sort by entity ID return ret; } static CComponentManager::InterfaceListUnordered g_EmptyEntityMap; const CComponentManager::InterfaceListUnordered& CComponentManager::GetEntitiesWithInterfaceUnordered(InterfaceId iid) const { if ((size_t)iid >= m_ComponentsByInterface.size()) { // Invalid iid return g_EmptyEntityMap; } return m_ComponentsByInterface[iid]; } void CComponentManager::PostMessage(entity_id_t ent, const CMessage& msg) { PROFILE2_IFSPIKE("Post Message", 0.0005); PROFILE2_ATTR("%s", msg.GetScriptHandlerName()); // Send the message to components of ent, that subscribed locally to this message std::map >::const_iterator it; it = m_LocalMessageSubscriptions.find(msg.GetType()); if (it != m_LocalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { // Find the component instances of this type (if any) std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; // Send the message to all of them std::map::const_iterator eit = emap->second.find(ent); if (eit != emap->second.end()) eit->second->HandleMessage(msg, false); } } SendGlobalMessage(ent, msg); } void CComponentManager::BroadcastMessage(const CMessage& msg) { // Send the message to components of all entities that subscribed locally to this message std::map >::const_iterator it; it = m_LocalMessageSubscriptions.find(msg.GetType()); if (it != m_LocalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { // Find the component instances of this type (if any) std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; // Send the message to all of them std::map::const_iterator eit = emap->second.begin(); for (; eit != emap->second.end(); ++eit) eit->second->HandleMessage(msg, false); } } SendGlobalMessage(INVALID_ENTITY, msg); } void CComponentManager::SendGlobalMessage(entity_id_t ent, const CMessage& msg) { PROFILE2_IFSPIKE("SendGlobalMessage", 0.001); PROFILE2_ATTR("%s", msg.GetScriptHandlerName()); // (Common functionality for PostMessage and BroadcastMessage) // Send the message to components of all entities that subscribed globally to this message std::map >::const_iterator it; it = m_GlobalMessageSubscriptions.find(msg.GetType()); if (it != m_GlobalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { // Special case: Messages for local entities shouldn't be sent to script // components that subscribed globally, so that we don't have to worry about // them accidentally picking up non-network-synchronised data. if (ENTITY_IS_LOCAL(ent)) { std::map::const_iterator cit = m_ComponentTypesById.find(*ctit); if (cit != m_ComponentTypesById.end() && cit->second.type == CT_Script) continue; } // Find the component instances of this type (if any) std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; // Send the message to all of them std::map::const_iterator eit = emap->second.begin(); for (; eit != emap->second.end(); ++eit) eit->second->HandleMessage(msg, true); } } // Send the message to component instances that dynamically subscribed to this message std::map::iterator dit = m_DynamicMessageSubscriptionsNonsync.find(msg.GetType()); if (dit != m_DynamicMessageSubscriptionsNonsync.end()) { dit->second.Flatten(); const std::vector& dynamic = dit->second.GetComponents(); for (size_t i = 0; i < dynamic.size(); i++) dynamic[i]->HandleMessage(msg, false); } } std::string CComponentManager::GenerateSchema() const { std::string schema = "" "" "" "" "" "0" "" "" "0" "" "" "" "" "" "" "" "" "" "" "" "" ""; std::map > interfaceComponentTypes; std::vector componentTypes; for (std::map::const_iterator it = m_ComponentTypesById.begin(); it != m_ComponentTypesById.end(); ++it) { schema += "" "" "" + it->second.schema + "" "" ""; interfaceComponentTypes[it->second.iid].push_back(it->second.name); componentTypes.push_back(it->second.name); } // Declare the implementation of each interface, for documentation for (std::map::const_iterator it = m_InterfaceIdsByName.begin(); it != m_InterfaceIdsByName.end(); ++it) { schema += ""; std::vector& cts = interfaceComponentTypes[it->second]; for (size_t i = 0; i < cts.size(); ++i) schema += ""; schema += ""; } // List all the component types, in alphabetical order (to match the reordering performed by CParamNode). // (We do it this way, rather than ing all the interface definitions (which would additionally perform // a check that we don't use multiple component types of the same interface in one file), because libxml2 gives // useless error messages in the latter case; this way lets it report the real error.) std::sort(componentTypes.begin(), componentTypes.end()); schema += "" "" "" ""; for (std::vector::const_iterator it = componentTypes.begin(); it != componentTypes.end(); ++it) schema += ""; schema += "" ""; schema += ""; return schema; } Index: ps/trunk/source/simulation2/system/Message.h =================================================================== --- ps/trunk/source/simulation2/system/Message.h (revision 27727) +++ ps/trunk/source/simulation2/system/Message.h (revision 27728) @@ -1,43 +1,45 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_MESSAGE #define INCLUDED_MESSAGE #include "scriptinterface/ScriptTypes.h" +class ScriptRequest; + class CMessage { NONCOPYABLE(CMessage); protected: CMessage() { } public: virtual ~CMessage() { } virtual int GetType() const = 0; virtual const char* GetScriptHandlerName() const = 0; virtual const char* GetScriptGlobalHandlerName() const = 0; - virtual JS::Value ToJSVal(const ScriptInterface&) const = 0; - JS::Value ToJSValCached(const ScriptInterface&) const; + virtual JS::Value ToJSVal(const ScriptRequest&) const = 0; + JS::Value ToJSValCached(const ScriptRequest&) const; private: mutable std::unique_ptr m_Cached; }; // TODO: GetType could be replaced with a plain member variable to avoid some // virtual calls, if that turns out to be worthwhile -CMessage* CMessageFromJSVal(int mtid, const ScriptInterface&, JS::HandleValue); +CMessage* CMessageFromJSVal(int mtid, const ScriptRequest&, JS::HandleValue); #endif // INCLUDED_MESSAGE