1
0
forked from 0ad/0ad

# Added initial support for players and population counters in new simulation system, plus various infrastructure improvements.

Merge from 22b478ffed8d.
Pure scripted interface definitions.
Entity creation from scripts.
Improved messaging system.
Messages on entity deletion.
Basic player entities.
Player ownership.
Bug fixes.

This was SVN commit r7281.
This commit is contained in:
Ykkrosh 2010-01-22 20:03:14 +00:00
parent 33882ab698
commit 4fed9b8242
57 changed files with 1312 additions and 207 deletions

View File

@ -8,4 +8,4 @@ TestScript2A.prototype.GetX = function() {
return this.x;
};
Engine.RegisterComponentType(IID_Test2, "TestScript2A", TestScript2A);
Engine.RegisterComponentType(IID_Test2, "TestScript2A", TestScript2A);

View File

@ -0,0 +1 @@
Engine.RegisterInterface("TestScriptIfc");

View File

@ -0,0 +1,11 @@
function TestScript1_AddEntity() {}
TestScript1_AddEntity.prototype.GetX = function()
{
if (Engine.AddEntity("bogus-template-name") !== 0)
throw new Error("bogus AddEntity failed");
return Engine.AddEntity("test1");
};
Engine.RegisterComponentType(IID_Test1, "TestScript1_AddEntity", TestScript1_AddEntity);

View File

@ -0,0 +1,18 @@
function TestScript1_Interface() {}
TestScript1_Interface.prototype.GetX = function()
{
return IID_TestScriptIfc + Engine.QueryInterface(this.entity, IID_TestScriptIfc).Method();
};
Engine.RegisterComponentType(IID_Test1, "TestScript1_Interface", TestScript1_Interface);
function TestScript2_Interface() {}
TestScript2_Interface.prototype.Method = function()
{
return 1000;
};
Engine.RegisterComponentType(IID_TestScriptIfc, "TestScript2_Interface", TestScript2_Interface);

View File

@ -10,11 +10,29 @@ TestScript1A.prototype.GetX = function() {
TestScript1A.prototype.OnUpdate = function(msg) {
this.x += msg.turnLength;
}
};
Engine.RegisterComponentType(IID_Test1, "TestScript1A", TestScript1A);
// -------- //
function TestScript1B() {}
TestScript1B.prototype.Init = function() {
this.x = 100;
};
TestScript1B.prototype.GetX = function() {
return this.x;
};
TestScript1B.prototype.OnGlobalUpdate = function(msg) {
this.x += msg.turnLength;
};
Engine.RegisterComponentType(IID_Test1, "TestScript1B", TestScript1B);
function TestScript2A() {}

View File

@ -0,0 +1,12 @@
function Cost() {}
Cost.prototype.Init = function()
{
};
Cost.prototype.GetPopCost = function()
{
return +this.template.Population;
};
Engine.RegisterComponentType(IID_Cost, "Cost", Cost);

View File

@ -0,0 +1,44 @@
function Player() {}
Player.prototype.Init = function()
{
this.playerID = undefined;
this.playerName = "Unknown";
this.civ = "celt";
this.popCount = 0;
this.popLimit = 50;
};
Player.prototype.SetPlayerID = function(id)
{
this.playerID = id;
};
Player.prototype.GetPopulationCount = function()
{
return this.popCount;
};
Player.prototype.GetPopulationLimit = function()
{
return this.popLimit;
};
Player.prototype.OnGlobalOwnershipChanged = function(msg)
{
if (msg.from == this.playerID)
{
var cost = Engine.QueryInterface(msg.entity, IID_Cost);
if (cost)
this.popCount -= cost.GetPopCost();
}
if (msg.to == this.playerID)
{
var cost = Engine.QueryInterface(msg.entity, IID_Cost);
if (cost)
this.popCount += cost.GetPopCost();
}
};
Engine.RegisterComponentType(IID_Player, "Player", Player);

View File

@ -0,0 +1,27 @@
function PlayerManager() {}
PlayerManager.prototype.Init = function()
{
this.playerEntities = []; // list of player entity IDs
};
PlayerManager.prototype.AddPlayer = function(ent)
{
var id = this.playerEntities.length;
Engine.QueryInterface(ent, IID_Player).SetPlayerID(id);
this.playerEntities.push(ent);
return id;
};
PlayerManager.prototype.GetPlayerByID = function(id)
{
return this.playerEntities[id];
// TODO: report error message if invalid id
};
PlayerManager.prototype.GetNumPlayers = function()
{
return this.playerEntities.length;
};
Engine.RegisterComponentType(IID_PlayerManager, "PlayerManager", PlayerManager);

View File

@ -0,0 +1 @@
Engine.RegisterInterface("Cost");

View File

@ -1,3 +1,4 @@
var g_NewIID = 1000; // some arbitrary not-yet-used number
var g_ComponentTypes = {};
var g_Components = {};
@ -7,14 +8,19 @@ Engine.RegisterComponentType = function(iid, name, ctor)
{
TS_ASSERT(!g_ComponentTypes[name]);
g_ComponentTypes[name] = { iid: iid, ctor: ctor };
}
};
Engine.RegisterInterface = function(name)
{
global["IID_"+name] = g_NewIID++;
};
Engine.QueryInterface = function(ent, iid)
{
if (g_Components[ent] && g_Components[ent][iid])
return g_Components[ent][iid];
return null;
}
};
// TODO:
// Engine.RegisterGlobal
@ -26,7 +32,7 @@ global.AddMock = function(ent, iid, mock)
if (!g_Components[ent])
g_Components[ent] = {};
g_Components[ent][iid] = mock;
}
};
global.ConstructComponent = function(ent, name, template)
{
@ -35,4 +41,4 @@ global.ConstructComponent = function(ent, name, template)
cmp.template = template;
cmp.Init();
return cmp;
}
};

View File

@ -0,0 +1,6 @@
Engine.LoadComponentScript("Player.js");
var cmp = ConstructComponent(10, "Player");
TS_ASSERT_EQUALS(cmp.GetPopulationCount(), 0);
TS_ASSERT_EQUALS(cmp.GetPopulationLimit(), 50);

View File

@ -0,0 +1,8 @@
function InitGame(data)
{
// This will be called after the map has been loaded, before the simulation has started.
// It should initialise the game based on parameters passed from the GUI, e.g. player
// names and colours.
}
Engine.RegisterGlobal("InitGame", InitGame);

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity>
<Player/>
</Entity>

View File

@ -5,5 +5,6 @@
<Anchor>upright</Anchor>
<Floating>false</Floating>
</Position>
<Ownership/>
<Selectable/>
</Entity>

View File

@ -5,5 +5,6 @@
<Anchor>upright</Anchor>
<Floating>false</Floating>
</Position>
<Ownership/>
<MotionBallScripted/>
</Entity>

View File

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_entity_full">
<UnitMotion/>
<Cost>
<Population>1</Population>
</Cost>
</Entity>

View File

@ -45,6 +45,9 @@
#include "simulation/EntityTemplate.h"
#include "simulation/EntityTemplateCollection.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPlayer.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpPosition.h"
#define LOG_CATEGORY L"graphics"
@ -306,6 +309,34 @@ void CXMLReader::Init(const VfsPath& xml_filename)
total_jobs = 0;
for (int i = 0; i < nodes.Count; i++)
total_jobs += nodes.Item(i).GetChildNodes().Count;
if (g_UseSimulation2)
{
// Find the maximum entity ID, so we can safely allocate new IDs without conflicts
int max_uid = SYSTEM_ENTITY;
XMBElement ents = nodes.GetFirstNamedItem(xmb_file.GetElementID("Entities"));
XERO_ITER_EL(ents, ent)
{
utf16string uid = ent.GetAttributes().GetNamedItem(at_uid);
max_uid = std::max(max_uid, CStr(uid).ToInt());
}
// Initialise player data
CmpPtr<ICmpPlayerManager> cmpPlayerMan(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
debug_assert(!cmpPlayerMan.null());
// TODO: this should be loaded from the XML instead
size_t numPlayers = 4;
for (size_t i = 0; i < numPlayers; ++i)
{
int uid = ++max_uid;
entity_id_t ent = g_Game->GetSimulation2()->AddEntity(L"special/player", uid);
cmpPlayerMan->AddPlayer(ent);
}
}
}
@ -778,6 +809,7 @@ int CXMLReader::ReadEntities(XMBElement parent, double end_time)
int EntityUid = CStr(uid).ToInt();
CStrW TemplateName;
int PlayerID = 0;
CFixedVector3D Position;
CFixedVector3D Orientation;
@ -790,6 +822,11 @@ int CXMLReader::ReadEntities(XMBElement parent, double end_time)
{
TemplateName = setting.GetText();
}
// <player>
else if (element_name == el_player)
{
PlayerID = CStr(setting.GetText()).ToInt();
}
// <position>
else if (element_name == el_position)
{
@ -826,9 +863,12 @@ int CXMLReader::ReadEntities(XMBElement parent, double end_time)
{
cmpPosition->JumpTo(Position.X, Position.Z);
cmpPosition->SetYRotation(Orientation.Y);
// TODO: other components
// TODO: other parts of the position
}
// TODO: load all the other data too
CmpPtr<ICmpOwnership> cmpOwner(sim, ent);
if (!cmpOwner.null())
cmpOwner->SetOwner(PlayerID);
}
completed_jobs++;
@ -949,7 +989,10 @@ int CXMLReader::ReadOldEntities(XMBElement parent, double end_time)
cmpPos->JumpTo(x, z);
cmpPos->SetYRotation(CFixed_23_8::FromFloat(Orientation));
}
// TODO: load player ID too
CmpPtr<ICmpOwnership> cmpOwner(*g_Game->GetSimulation2(), ent);
if (!cmpOwner.null())
cmpOwner->SetOwner(PlayerID);
}
}
else

View File

@ -46,6 +46,7 @@
#include "simulation/TriggerManager.h"
#include "simulation/Entity.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpTemplateManager.h"
@ -294,12 +295,18 @@ void CMapWriter::WriteXML(const VfsPath& filename,
{
entity_id_t ent = it->first;
// Don't save local entities (placement previews etc)
if (ENTITY_IS_LOCAL(ent))
continue;
XML_Element("Entity");
XML_Attribute("uid", ent);
XML_Setting("Template", cmpTemplateManager->GetCurrentTemplateName(ent));
// TODO: player id
CmpPtr<ICmpOwnership> cmpOwner(sim, ent);
if (!cmpOwner.null())
XML_Setting("Player", (int)cmpOwner->GetOwner());
CmpPtr<ICmpPosition> cmpPosition(sim, ent);
if (!cmpPosition.null())

View File

@ -84,9 +84,13 @@ CGame::CGame():
if (g_UseSimulation2)
{
m_Simulation2->LoadScripts(L"simulation/components/interfaces/");
m_Simulation2->LoadScripts(L"simulation/helpers/");
m_Simulation2->LoadScripts(L"simulation/components/");
m_Simulation2->ResetState();
CScriptVal initData; // TODO: ought to get this from the GUI, somehow
m_Simulation2->InitGame(initData);
}
}

View File

@ -165,11 +165,17 @@ std::string XMBFile::GetAttributeString(const int ID) const
int XMBElement::GetNodeName() const
{
if (m_Pointer == NULL)
return -1;
return *(int*)(m_Pointer + 4); // == ElementName
}
XMBElementList XMBElement::GetChildNodes() const
{
if (m_Pointer == NULL)
return XMBElementList(NULL, 0);
return XMBElementList(
m_Pointer + 20 + *(int*)(m_Pointer + 16), // == Children[]
*(int*)(m_Pointer + 12) // == ChildCount
@ -178,6 +184,9 @@ XMBElementList XMBElement::GetChildNodes() const
XMBAttributeList XMBElement::GetAttributes() const
{
if (m_Pointer == NULL)
return XMBAttributeList(NULL, 0);
return XMBAttributeList(
m_Pointer + 24 + *(int*)(m_Pointer + 20), // == Attributes[]
*(int*)(m_Pointer + 8) // == AttributeCount
@ -187,7 +196,7 @@ XMBAttributeList XMBElement::GetAttributes() const
utf16string XMBElement::GetText() const
{
// Return empty string if there's no text
if (*(int*)(m_Pointer + 20) == 0)
if (m_Pointer == NULL || *(int*)(m_Pointer + 20) == 0)
return utf16string();
else
return utf16string((utf16_t*)(m_Pointer + 28));
@ -196,12 +205,31 @@ utf16string XMBElement::GetText() const
int XMBElement::GetLineNumber() const
{
// Make sure there actually was some text to record the line of
if (*(int*)(m_Pointer + 20) == 0)
if (m_Pointer == NULL || *(int*)(m_Pointer + 20) == 0)
return -1;
else
return *(int*)(m_Pointer + 24);
}
XMBElement XMBElementList::GetFirstNamedItem(const int ElementName) const
{
const char* Pos = m_Pointer;
// Maybe not the cleverest algorithm, but it should be
// fast enough with half a dozen attributes:
for (int i = 0; i < Count; ++i)
{
int Length = *(int*)Pos;
int Name = *(int*)(Pos+4);
if (Name == ElementName)
return XMBElement(Pos);
Pos += Length;
}
// Can't find element
return XMBElement();
}
XMBElement XMBElementList::Item(const int id)
{
debug_assert(id >= 0 && id < Count && "Element ID out of range");

View File

@ -166,7 +166,6 @@ private:
class XMBElement
{
public:
// janwas: default ctor needed for ReadXML
XMBElement()
: m_Pointer(0) {}
@ -189,7 +188,6 @@ private:
class XMBElementList
{
public:
// janwas: default ctor needed for ReadXML
XMBElementList()
: Count(0), m_Pointer(0), m_LastItemID(-2) {}
@ -198,6 +196,10 @@ public:
m_Pointer(offset),
m_LastItemID(-2) {} // use -2 because it isn't x-1 where x is a non-negative integer
// Get first element in list with the given name.
// Performance is linear in the number of elements in the list.
XMBElement GetFirstNamedItem(const int ElementName) const;
XMBElement Item(const int id); // returns Children[id]
int Count;
@ -227,7 +229,7 @@ public:
XMBAttributeList(const char* offset, int count)
: Count(count), m_Pointer(offset), m_LastItemID(-2) {};
// Get the attribute value directly (unlike Xerces)
// Get the attribute value directly
utf16string GetNamedItem(const int AttributeName) const;
// Returns an attribute by position in the list

View File

@ -81,6 +81,30 @@ public:
TS_ASSERT_EQUALS(CStr(attr.Value), " y ");
}
void test_GetFirstNamedItem()
{
XMBFile xmb (parse("<test> <x>A</x> <x>B</x> <y>C</y> <z>D</z> </test>"));
XMBElement root = xmb.GetRoot();
TS_ASSERT_EQUALS(root.GetChildNodes().Count, 4);
XMBElement x = root.GetChildNodes().GetFirstNamedItem(xmb.GetElementID("x"));
XMBElement y = root.GetChildNodes().GetFirstNamedItem(xmb.GetElementID("y"));
XMBElement w = root.GetChildNodes().GetFirstNamedItem(xmb.GetElementID("w"));
TS_ASSERT_EQUALS(x.GetNodeName(), xmb.GetElementID("x"));
TS_ASSERT_EQUALS(CStr(x.GetText()), "A");
TS_ASSERT_EQUALS(y.GetNodeName(), xmb.GetElementID("y"));
TS_ASSERT_EQUALS(CStr(y.GetText()), "C");
TS_ASSERT_EQUALS(w.GetNodeName(), -1);
TS_ASSERT_EQUALS(CStr(w.GetText()), "");
TS_ASSERT_EQUALS(w.GetLineNumber(), -1);
TS_ASSERT_EQUALS(w.GetChildNodes().Count, 0);
TS_ASSERT_EQUALS(w.GetAttributes().Count, 0);
}
void test_doctype_ignored()
{
XMBFile xmb (parse("<!DOCTYPE foo SYSTEM \"file:///dev/urandom\"><foo/>"));

View File

@ -19,6 +19,7 @@
#define INCLUDED_MESSAGETYPES
#include "simulation2/system/Components.h"
#include "simulation2/system/Entity.h"
#include "simulation2/system/Message.h"
#include "maths/Fixed.h"
@ -26,6 +27,7 @@
#define DEFAULT_MESSAGE_IMPL(name) \
virtual EMessageTypeId GetType() const { return MT_##name; } \
virtual const char* GetScriptHandlerName() const { return "On" #name; } \
virtual const char* GetScriptGlobalHandlerName() const { return "OnGlobal" #name; } \
virtual jsval ToJSVal(ScriptInterface& scriptInterface) const; \
static CMessage* FromJSVal(ScriptInterface&, jsval val);
@ -83,4 +85,37 @@ public:
bool culling;
};
/**
* 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 : public CMessage
{
public:
DEFAULT_MESSAGE_IMPL(Destroy)
CMessageDestroy()
{
}
};
class CMessageOwnershipChanged : public CMessage
{
public:
DEFAULT_MESSAGE_IMPL(OwnershipChanged)
CMessageOwnershipChanged(entity_id_t entity, int32_t from, int32_t to) :
entity(entity), from(from), to(to)
{
}
entity_id_t entity;
int32_t from;
int32_t to;
};
#endif // INCLUDED_MESSAGETYPES

View File

@ -47,7 +47,7 @@ public:
// (can't call ResetState here since the scripts haven't been loaded yet)
}
void ResetState(bool skipGui)
void ResetState(bool skipScriptedComponents)
{
m_ComponentManager.ResetState();
@ -62,22 +62,24 @@ public:
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_CommandQueue, noParam);
// Add scripted system components:
if (!skipGui)
if (!skipScriptedComponents)
{
cid = m_ComponentManager.LookupCID("GuiInterface");
if (cid == CID__Invalid)
LOGERROR(L"Can't find component type GuiInterface");
m_ComponentManager.AddComponent(SYSTEM_ENTITY, cid, noParam);
#define LOAD_SCRIPTED_COMPONENT(name) \
cid = m_ComponentManager.LookupCID(name); \
if (cid == CID__Invalid) \
LOGERROR(L"Can't find component type " name); \
m_ComponentManager.AddComponent(SYSTEM_ENTITY, cid, noParam)
LOAD_SCRIPTED_COMPONENT("GuiInterface");
LOAD_SCRIPTED_COMPONENT("PlayerManager");
#undef LOAD_SCRIPTED_COMPONENT
}
}
bool LoadScripts(const VfsPath& path);
LibError ReloadChangedFile(const VfsPath& path);
void AddComponent(entity_id_t ent, EComponentTypeId cid, const CParamNode& paramNode);
entity_id_t AddEntity(const std::wstring& templateName, entity_id_t ent);
void Update(float frameTime);
void Interpolate(float frameTime);
@ -129,49 +131,6 @@ LibError CSimulation2Impl::ReloadChangedFile(const VfsPath& path)
return INFO::OK;
}
void CSimulation2Impl::AddComponent(entity_id_t ent, EComponentTypeId cid, const CParamNode& paramNode)
{
m_ComponentManager.AddComponent(ent, cid, paramNode);
}
entity_id_t CSimulation2Impl::AddEntity(const std::wstring& templateName, entity_id_t ent)
{
CmpPtr<ICmpTemplateManager> tempMan(m_SimContext, SYSTEM_ENTITY);
debug_assert(!tempMan.null());
// TODO: should assert that ent doesn't exist
const CParamNode* tmpl = tempMan->LoadTemplate(ent, templateName, -1);
if (!tmpl)
return INVALID_ENTITY; // LoadTemplate will have reported the error
// 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 = m_ComponentManager.LookupCID(it->first);
if (cid == CID__Invalid)
{
LOGERROR(L"Unrecognised component type name '%hs' in entity template '%ls'", it->first.c_str(), templateName.c_str());
return INVALID_ENTITY;
}
if (!m_ComponentManager.AddComponent(ent, cid, it->second))
{
LOGERROR(L"Failed to construct component type name '%hs' in entity template '%ls'", it->first.c_str(), templateName.c_str());
return INVALID_ENTITY;
}
// TODO: maybe we should delete already-constructed components if one of them fails?
}
return ent;
}
void CSimulation2Impl::Update(float frameTime)
{
// TODO: Use CTurnManager
@ -189,6 +148,9 @@ void CSimulation2Impl::Update(float frameTime)
cmpCommandQueue->ProcessCommands();
m_ComponentManager.BroadcastMessage(CMessageUpdate(turnLengthFixed));
// Clean up any entities destroyed during the simulation update
m_ComponentManager.FlushDestroyedComponents();
}
}
@ -212,19 +174,21 @@ CSimulation2::~CSimulation2()
delete m;
}
// Forward all method calls to the appropriate CSimulation2Impl/CComponentManager methods:
entity_id_t CSimulation2::AddEntity(const std::wstring& templateName)
{
return m->AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity());
return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity());
}
entity_id_t CSimulation2::AddEntity(const std::wstring& templateName, entity_id_t preferredId)
{
return m->AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity(preferredId));
return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity(preferredId));
}
entity_id_t CSimulation2::AddLocalEntity(const std::wstring& templateName)
{
return m->AddEntity(templateName, m->m_ComponentManager.AllocateNewLocalEntity());
return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewLocalEntity());
}
void CSimulation2::DestroyEntity(entity_id_t ent)
@ -267,6 +231,12 @@ ScriptInterface& CSimulation2::GetScriptInterface() const
return m->m_ComponentManager.GetScriptInterface();
}
void CSimulation2::InitGame(const CScriptVal& data)
{
CScriptVal ret; // ignored
GetScriptInterface().CallFunction(GetScriptInterface().GetGlobalObject(), "InitGame", data, ret);
}
void CSimulation2::Update(float frameTime)
{
m->Update(frameTime);

View File

@ -32,6 +32,7 @@ class CTerrain;
class IComponent;
class ScriptInterface;
class CMessage;
class CScriptVal;
// Hopefully-temporary flag for transition to new simulation system
extern bool g_UseSimulation2;
@ -64,9 +65,17 @@ public:
* Initialise (or re-initialise) the complete simulation state.
* Must be called after LoadScripts, and must be called
* before any methods that depend on the simulation state.
* @param skipGui don't load the GUI interface components (this is intended for use by test cases)
* @param skipScriptedComponents don't load the scripted system components
* (this is intended for use by test cases that don't mount all of VFS)
*/
void ResetState(bool skipGui = false);
void ResetState(bool skipScriptedComponents = false);
/**
* Initialise a new game, based on some script data.
* (This mustn't be used when e.g. loading saved games, only when starting new ones.)
* This calls the InitGame function defined in helpers/InitGame.js.
*/
void InitGame(const CScriptVal& data);
void Update(float frameTime);
void Interpolate(float frameTime);
@ -88,7 +97,9 @@ public:
/**
* Does the actual destruction of entities from DestroyEntity.
* This should be called at the beginning of each frame or after an Update message.
* This is called automatically by Update, but should also be called at other
* times when an entity might have been deleted and should be removed from
* any further processing (e.g. after editor UI message processing)
*/
void FlushDestroyedEntities();

View File

@ -34,33 +34,50 @@ MESSAGE(TurnStart)
MESSAGE(Update)
MESSAGE(Interpolate) // non-deterministic (use with caution)
MESSAGE(RenderSubmit) // non-deterministic (use with caution)
MESSAGE(Destroy)
MESSAGE(OwnershipChanged)
// TemplateManager must come before all other (non-test) components,
// so that it is the first to be (de)serialized
INTERFACE(TemplateManager)
COMPONENT(TemplateManager)
// Special component for script component types with no native interface
INTERFACE(UnknownScript)
COMPONENT(UnknownScript)
// In alphabetical order:
INTERFACE(CommandQueue)
COMPONENT(CommandQueue)
INTERFACE(GuiInterface)
COMPONENT(GuiInterfaceScripted)
INTERFACE(Terrain)
COMPONENT(Terrain)
INTERFACE(Position)
COMPONENT(Position)
INTERFACE(Visual)
COMPONENT(VisualActor)
INTERFACE(Motion)
COMPONENT(MotionBall)
COMPONENT(MotionScripted)
INTERFACE(UnitMotion)
COMPONENT(UnitMotion)
INTERFACE(Ownership)
COMPONENT(Ownership)
INTERFACE(Player)
COMPONENT(PlayerScripted)
INTERFACE(PlayerManager)
COMPONENT(PlayerManagerScripted)
INTERFACE(Position)
COMPONENT(Position)
INTERFACE(Selectable)
COMPONENT(Selectable)
INTERFACE(CommandQueue)
COMPONENT(CommandQueue)
INTERFACE(Terrain)
COMPONENT(Terrain)
INTERFACE(UnitMotion)
COMPONENT(UnitMotion)
INTERFACE(Visual)
COMPONENT(VisualActor)

View File

@ -30,7 +30,7 @@ class CCmpMotionBall : public ICmpMotion
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(CID_MotionBall, MT_Update);
componentManager.SubscribeToMessageType(MT_Update);
}
DEFAULT_COMPONENT_ALLOCATOR(MotionBall)
@ -61,7 +61,7 @@ public:
deserialize.NumberFloat_Unbounded(m_SpeedZ);
}
virtual void HandleMessage(const CSimContext& context, const CMessage& msg)
virtual void HandleMessage(const CSimContext& context, const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{

View File

@ -0,0 +1,96 @@
/* Copyright (C) 2010 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 <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpOwnership.h"
#include "simulation2/MessageTypes.h"
/**
* Basic ICmpOwnership implementation.
*/
class CCmpOwnership : public ICmpOwnership
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_Destroy);
}
DEFAULT_COMPONENT_ALLOCATOR(Ownership)
const CSimContext* m_Context; // never NULL (after Init/Deserialize)
int32_t m_Owner;
virtual void Init(const CSimContext& context, const CParamNode& UNUSED(paramNode))
{
m_Context = &context;
m_Owner = -1;
}
virtual void Deinit(const CSimContext& UNUSED(context))
{
}
virtual void Serialize(ISerializer& serialize)
{
serialize.NumberI32_Unbounded("owner", m_Owner);
}
virtual void Deserialize(const CSimContext& context, const CParamNode& UNUSED(paramNode), IDeserializer& deserialize)
{
m_Context = &context;
deserialize.NumberI32_Unbounded(m_Owner);
}
virtual void HandleMessage(const CSimContext& UNUSED(context), const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_Destroy:
{
// Reset the owner so this entity is e.g. removed from population counts
SetOwner(-1);
break;
}
}
}
virtual int32_t GetOwner()
{
return m_Owner;
}
virtual void SetOwner(int32_t playerID)
{
if (playerID == m_Owner)
return;
int32_t old = m_Owner;
m_Owner = playerID;
CMessageOwnershipChanged msg(GetEntityId(), old, playerID);
m_Context->GetComponentManager().PostMessage(GetEntityId(), msg);
}
};
REGISTER_COMPONENT_TYPE(Ownership)

View File

@ -37,7 +37,7 @@ class CCmpPosition : public ICmpPosition
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(CID_Position, MT_TurnStart);
componentManager.SubscribeToMessageType(MT_TurnStart);
// TODO: if this component turns out to be a performance issue, it should
// be optimised by creating a new PositionStatic component that doesn't subscribe
@ -284,7 +284,7 @@ public:
return mXZ;
}
virtual void HandleMessage(const CSimContext&, const CMessage& msg)
virtual void HandleMessage(const CSimContext&, const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{

View File

@ -36,8 +36,8 @@ class CCmpSelectable : public ICmpSelectable
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(CID_Selectable, MT_Interpolate);
componentManager.SubscribeToMessageType(CID_Selectable, MT_RenderSubmit);
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_RenderSubmit);
// TODO: it'd be nice if we didn't get these messages except in the rare
// cases where we're actually drawing a selection highlight
}
@ -67,7 +67,7 @@ public:
{
}
virtual void HandleMessage(const CSimContext& context, const CMessage& msg)
virtual void HandleMessage(const CSimContext& context, const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{

View File

@ -74,7 +74,7 @@ public:
}
}
virtual void HandleMessage(const CSimContext& UNUSED(context), const CMessage& UNUSED(msg))
virtual void HandleMessage(const CSimContext& UNUSED(context), const CMessage& UNUSED(msg), bool UNUSED(global))
{
// TODO: should listen to entity destruction messages, to clean up m_LatestTemplates
}
@ -305,6 +305,7 @@ void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& i
// and safe (i.e. won't do anything that affects the synchronised simulation state), so additions
// to this list should be carefully considered
std::set<std::string> permittedComponentTypes;
permittedComponentTypes.insert("Ownership");
permittedComponentTypes.insert("Position");
permittedComponentTypes.insert("VisualActor");
// (This could be initialised once and reused, but it's not worth the effort)

View File

@ -28,7 +28,8 @@ class CCmpTest1A : public ICmpTest1
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(CID_Test1A, MT_TurnStart);
componentManager.SubscribeToMessageType(MT_TurnStart);
componentManager.SubscribeToMessageType(MT_Interpolate);
}
DEFAULT_COMPONENT_ALLOCATOR(Test1A)
@ -62,13 +63,16 @@ public:
return m_x;
}
virtual void HandleMessage(const CSimContext&, const CMessage& msg)
virtual void HandleMessage(const CSimContext&, const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_TurnStart:
m_x += 1;
break;
case MT_Interpolate:
m_x += 2;
break;
default:
m_x = 0;
break;
@ -83,7 +87,8 @@ class CCmpTest1B : public ICmpTest1
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(CID_Test1B, MT_Update);
componentManager.SubscribeToMessageType(MT_Update);
componentManager.SubscribeGloballyToMessageType(MT_Interpolate);
}
DEFAULT_COMPONENT_ALLOCATOR(Test1B)
@ -114,13 +119,16 @@ public:
return m_x;
}
virtual void HandleMessage(const CSimContext&, const CMessage& msg)
virtual void HandleMessage(const CSimContext&, const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_Update:
m_x += 10;
break;
case MT_Interpolate:
m_x += 20;
break;
default:
m_x = 0;
break;
@ -135,8 +143,8 @@ class CCmpTest2A : public ICmpTest2
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(CID_Test2A, MT_TurnStart);
componentManager.SubscribeToMessageType(CID_Test2A, MT_Update);
componentManager.SubscribeToMessageType(MT_TurnStart);
componentManager.SubscribeToMessageType(MT_Update);
}
DEFAULT_COMPONENT_ALLOCATOR(Test2A)
@ -167,7 +175,7 @@ public:
return m_x;
}
virtual void HandleMessage(const CSimContext&, const CMessage& msg)
virtual void HandleMessage(const CSimContext&, const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{

View File

@ -28,7 +28,7 @@ class CCmpUnitMotion : public ICmpUnitMotion
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(CID_UnitMotion, MT_Update);
componentManager.SubscribeToMessageType(MT_Update);
}
DEFAULT_COMPONENT_ALLOCATOR(UnitMotion)
@ -72,7 +72,7 @@ public:
}
}
virtual void HandleMessage(const CSimContext& context, const CMessage& msg)
virtual void HandleMessage(const CSimContext& context, const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{

View File

@ -40,8 +40,9 @@ class CCmpVisualActor : public ICmpVisual
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(CID_VisualActor, MT_Interpolate);
componentManager.SubscribeToMessageType(CID_VisualActor, MT_RenderSubmit);
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_RenderSubmit);
componentManager.SubscribeToMessageType(MT_OwnershipChanged);
}
DEFAULT_COMPONENT_ALLOCATOR(VisualActor)
@ -99,7 +100,7 @@ public:
Init(context, paramNode);
}
virtual void HandleMessage(const CSimContext& context, const CMessage& msg)
virtual void HandleMessage(const CSimContext& context, const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
@ -115,6 +116,13 @@ public:
RenderSubmit(context, msgData.collector, msgData.frustum, msgData.culling);
break;
}
case MT_OwnershipChanged:
{
const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
if (m_Unit)
m_Unit->SetPlayerID(msgData.to);
break;
}
}
}
@ -156,6 +164,9 @@ void CCmpVisualActor::Interpolate(const CSimContext& context, float frameOffset)
void CCmpVisualActor::RenderSubmit(const CSimContext& UNUSED(context), SceneCollector& collector, const CFrustum& frustum, bool culling)
{
if (m_Unit == NULL)
return;
// TODO: need to think about things like LOS here
CModel* model = m_Unit->GetModel();

View File

@ -0,0 +1,27 @@
/* Copyright (C) 2010 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 <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "ICmpOwnership.h"
#include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(Ownership)
DEFINE_INTERFACE_METHOD_0("GetOwner", int32_t, ICmpOwnership, GetOwner)
DEFINE_INTERFACE_METHOD_1("SetOwner", void, ICmpOwnership, SetOwner, int32_t)
END_INTERFACE_WRAPPER(Ownership)

View File

@ -0,0 +1,38 @@
/* Copyright (C) 2010 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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_ICMPOWNERSHIP
#define INCLUDED_ICMPOWNERSHIP
#include "simulation2/system/Interface.h"
/**
* Player ownership.
* Owner values are either a player ID (if >= 0), or unassigned (-1).
* Sends message OwnershipChanged after it changes.
*/
class ICmpOwnership : public IComponent
{
public:
virtual int32_t GetOwner() = 0;
virtual void SetOwner(int32_t playerID) = 0;
DECLARE_INTERFACE_TYPE(Ownership)
};
#endif // INCLUDED_ICMPOWNERSHIP

View File

@ -0,0 +1,39 @@
/* Copyright (C) 2010 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 <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "ICmpPlayer.h"
#include "simulation2/system/InterfaceScripted.h"
#include "simulation2/scripting/ScriptComponent.h"
BEGIN_INTERFACE_WRAPPER(Player)
END_INTERFACE_WRAPPER(Player)
class CCmpPlayerScripted : public ICmpPlayer
{
public:
DEFAULT_SCRIPT_WRAPPER(PlayerScripted)
virtual void SetName(const std::wstring& name)
{
m_Script.CallVoid("SetName", name);
}
};
REGISTER_COMPONENT_SCRIPT_WRAPPER(PlayerScripted)

View File

@ -0,0 +1,37 @@
/* Copyright (C) 2010 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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_ICMPPLAYER
#define INCLUDED_ICMPPLAYER
#include "simulation2/system/Interface.h"
/**
* Player data.
* (This interface only includes the functions needed by native code for loading maps;
* most player interaction is handled by scripts instead.)
*/
class ICmpPlayer : public IComponent
{
public:
virtual void SetName(const std::wstring& name) = 0;
// TODO: some more data
DECLARE_INTERFACE_TYPE(Player)
};
#endif // INCLUDED_ICMPPLAYER

View File

@ -0,0 +1,39 @@
/* Copyright (C) 2010 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 <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "ICmpPlayerManager.h"
#include "simulation2/system/InterfaceScripted.h"
#include "simulation2/scripting/ScriptComponent.h"
BEGIN_INTERFACE_WRAPPER(PlayerManager)
END_INTERFACE_WRAPPER(PlayerManager)
class CCmpPlayerManagerScripted : public ICmpPlayerManager
{
public:
DEFAULT_SCRIPT_WRAPPER(PlayerManagerScripted)
virtual void AddPlayer(entity_id_t ent)
{
m_Script.CallVoid("AddPlayer", (int)ent);
}
};
REGISTER_COMPONENT_SCRIPT_WRAPPER(PlayerManagerScripted)

View File

@ -0,0 +1,36 @@
/* Copyright (C) 2010 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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_ICMPPLAYERMANAGER
#define INCLUDED_ICMPPLAYERMANAGER
#include "simulation2/system/Interface.h"
/**
* Player manager. This maintains the list of players that exist in the game.
*/
class ICmpPlayerManager : public IComponent
{
public:
virtual void AddPlayer(entity_id_t ent) = 0;
// Accessors are currently only available to scripts, since no C++ code needed them yet
DECLARE_INTERFACE_TYPE(PlayerManager)
};
#endif // INCLUDED_ICMPPLAYERMANAGER

View File

@ -0,0 +1,34 @@
/* Copyright (C) 2010 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 <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "ICmpUnknownScript.h"
#include "simulation2/system/InterfaceScripted.h"
#include "simulation2/scripting/ScriptComponent.h"
BEGIN_INTERFACE_WRAPPER(UnknownScript)
END_INTERFACE_WRAPPER(UnknownScript)
class CCmpUnknownScript : public ICmpUnknownScript
{
public:
DEFAULT_SCRIPT_WRAPPER(UnknownScript)
};
REGISTER_COMPONENT_SCRIPT_WRAPPER(UnknownScript)

View File

@ -0,0 +1,32 @@
/* Copyright (C) 2010 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 <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_ICMPUNKNOWNSCRIPT
#define INCLUDED_ICMPUNKNOWNSCRIPT
#include "simulation2/system/Interface.h"
/**
* Dummy wrapper class for script components that don't have a native interface.
*/
class ICmpUnknownScript : public IComponent
{
public:
DECLARE_INTERFACE_TYPE(UnknownScript)
};
#endif // INCLUDED_ICMPUNKNOWNSCRIPT

View File

@ -86,7 +86,7 @@ public:
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(200, 60, 0));
// Latch new position for interpolation
test.HandleMessage(cmp, CMessageTurnStart());
test.HandleMessage(cmp, CMessageTurnStart(), false);
// Move smoothly to new position
cmp->MoveTo(entity_pos_t::FromInt(400), entity_pos_t::FromInt(300));

View File

@ -106,6 +106,38 @@ CMessage* CMessageRenderSubmit::FromJSVal(ScriptInterface& UNUSED(scriptInterfac
return NULL;
}
////////////////////////////////
jsval CMessageDestroy::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const
{
return JSVAL_VOID;
}
CMessage* CMessageDestroy::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val))
{
return new CMessageDestroy();
}
////////////////////////////////
jsval CMessageOwnershipChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(entity);
SET_MSG_PROPERTY(from);
SET_MSG_PROPERTY(to);
return OBJECT_TO_JSVAL(obj);
}
CMessage* CMessageOwnershipChanged::FromJSVal(ScriptInterface& scriptInterface, jsval val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(entity_id_t, entity);
GET_MSG_PROPERTY(int32_t, from);
GET_MSG_PROPERTY(int32_t, to);
return new CMessageOwnershipChanged(entity, from, to);
}
////////////////////////////////////////////////////////////////
CMessage* CMessageFromJSVal(int mtid, ScriptInterface& scriptingInterface, jsval val)

View File

@ -46,15 +46,16 @@ void CComponentTypeScript::Deinit(const CSimContext& UNUSED(context))
m_ScriptInterface.CallFunctionVoid(m_Instance, "Deinit");
}
void CComponentTypeScript::HandleMessage(const CSimContext& UNUSED(context), const CMessage& msg)
void CComponentTypeScript::HandleMessage(const CSimContext& UNUSED(context), const CMessage& msg, bool global)
{
const char* name = msg.GetScriptHandlerName();
const char* name = global ? msg.GetScriptGlobalHandlerName() : msg.GetScriptHandlerName();
CScriptVal msgVal = msg.ToJSVal(m_ScriptInterface);
// TODO: repeated conversions are exceedingly inefficient. Should
// cache this once per message (if it's used by >= 1 scripted component)
m_ScriptInterface.CallFunctionVoid(m_Instance, name, msgVal);
if (!m_ScriptInterface.CallFunctionVoid(m_Instance, name, msgVal))
LOGERROR(L"Script message handler %hs failed", name);
}
void CComponentTypeScript::Serialize(ISerializer& serialize)

View File

@ -22,6 +22,7 @@
#include "ps/CLogger.h"
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_trailing_params.hpp>
#include <boost/preprocessor/repetition/enum_trailing_binary_params.hpp>
@ -40,7 +41,7 @@ public:
void Init(const CSimContext& context, const CParamNode& paramNode, entity_id_t ent);
void Deinit(const CSimContext& context);
void HandleMessage(const CSimContext& context, const CMessage& msg);
void HandleMessage(const CSimContext& context, const CMessage& msg, bool global);
void Serialize(ISerializer& serialize);
void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& deserialize, entity_id_t ent);
@ -49,9 +50,26 @@ public:
// template<typename R> R Call(const char* funcname);
// template<typename R, typename T0> R Call(const char* funcname, const T0& a0);
// ...
// void CallVoid(const char* funcname);
// template<typename T0> void CallVoid(const char* funcname, const T0& a0);
// ...
#define OVERLOADS(z, i, data) \
template<typename R BOOST_PP_ENUM_TRAILING_PARAMS(i, typename T)> \
R Call(const char* funcname BOOST_PP_ENUM_TRAILING_BINARY_PARAMS(i, const T, &a));
R Call(const char* funcname BOOST_PP_ENUM_TRAILING_BINARY_PARAMS(i, const T, &a)) \
{ \
R ret; \
if (m_ScriptInterface.CallFunction(m_Instance, funcname BOOST_PP_ENUM_TRAILING_PARAMS(i, a), ret)) \
return ret; \
LOGERROR(L"Error calling component script function %hs", funcname); \
return R(); \
} \
BOOST_PP_IF(i, template<, ) BOOST_PP_ENUM_PARAMS(i, typename T) BOOST_PP_IF(i, >, ) \
void CallVoid(const char* funcname BOOST_PP_ENUM_TRAILING_BINARY_PARAMS(i, const T, &a)) \
{ \
if (m_ScriptInterface.CallFunctionVoid(m_Instance, funcname BOOST_PP_ENUM_TRAILING_PARAMS(i, a))) \
return; \
LOGERROR(L"Error calling component script function %hs", funcname); \
}
BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~)
#undef OVERLOADS
@ -62,17 +80,4 @@ private:
NONCOPYABLE(CComponentTypeScript);
};
#define OVERLOADS(z, i, data) \
template<typename R BOOST_PP_ENUM_TRAILING_PARAMS(i, typename T)> \
R CComponentTypeScript::Call(const char* funcname BOOST_PP_ENUM_TRAILING_BINARY_PARAMS(i, const T, &a)) \
{ \
R ret; \
if (m_ScriptInterface.CallFunction(m_Instance, funcname BOOST_PP_ENUM_TRAILING_PARAMS(i, a), ret)) \
return ret; \
LOGERROR(L"Error calling component script function %hs", funcname); \
return R(); \
}
BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~)
#undef OVERLOADS
#endif // INCLUDED_SCRIPTCOMPONENT

View File

@ -64,9 +64,9 @@
{ \
m_Script.Deinit(context); \
} \
virtual void HandleMessage(const CSimContext& context, const CMessage& msg) \
virtual void HandleMessage(const CSimContext& context, const CMessage& msg, bool global) \
{ \
m_Script.HandleMessage(context, msg); \
m_Script.HandleMessage(context, msg, global); \
} \
virtual void Serialize(ISerializer& serialize) \
{ \

View File

@ -20,8 +20,12 @@
#include "ComponentManager.h"
#include "IComponent.h"
#include "ParamNode.h"
#include "SimContext.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/MessageTypes.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
@ -29,18 +33,25 @@ CComponentManager::CComponentManager(const CSimContext& context, bool skipScript
m_NextScriptComponentTypeId(CID__LastNative), m_ScriptInterface("Engine"), m_SimContext(context), m_CurrentlyHotloading(false)
{
m_ScriptInterface.SetCallbackData(static_cast<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)
{
m_ScriptInterface.RegisterFunction<void, int, std::string, CScriptVal, CComponentManager::Script_RegisterComponentType> ("RegisterComponentType");
m_ScriptInterface.RegisterFunction<void, std::string, CComponentManager::Script_RegisterInterface> ("RegisterInterface");
m_ScriptInterface.RegisterFunction<void, std::string, CScriptVal, CComponentManager::Script_RegisterGlobal> ("RegisterGlobal");
m_ScriptInterface.RegisterFunction<IComponent*, int, int, CComponentManager::Script_QueryInterface> ("QueryInterface");
m_ScriptInterface.RegisterFunction<void, int, int, CScriptVal, CComponentManager::Script_PostMessage> ("PostMessage");
m_ScriptInterface.RegisterFunction<void, int, CScriptVal, CComponentManager::Script_BroadcastMessage> ("BroadcastMessage");
m_ScriptInterface.RegisterFunction<int, std::string, CComponentManager::Script_AddEntity> ("AddEntity");
}
// Define MT_*, IID_* as script globals
// 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);
#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
@ -63,6 +74,8 @@ CComponentManager::~CComponentManager()
m_ScriptInterface.RemoveRoot(&it->second.ctor);
}
void CComponentManager::LoadComponentTypes()
{
#define MESSAGE(name) \
RegisterMessageType(MT_##name, #name);
#define INTERFACE(name) \
@ -73,14 +86,15 @@ CComponentManager::~CComponentManager()
m_CurrentComponent = CID_##name; \
RegisterComponentType_##name(*this);
void CComponentManager::LoadComponentTypes()
{
#include "simulation2/TypeList.h"
}
m_CurrentComponent = CID__Invalid;
#undef MESSAGE
#undef INTERFACE
#undef COMPONENT
}
bool CComponentManager::LoadScript(const std::wstring& filename, bool hotload)
{
@ -93,13 +107,6 @@ bool CComponentManager::LoadScript(const std::wstring& filename, bool hotload)
return ok;
}
void CComponentManager::Script_RegisterGlobal(void* cbdata, std::string name, CScriptVal value)
{
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
componentManager->m_ScriptInterface.SetGlobal(name.c_str(), value);
}
void CComponentManager::Script_RegisterComponentType(void* cbdata, int iid, std::string cname, CScriptVal ctor)
{
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
@ -124,6 +131,8 @@ void CComponentManager::Script_RegisterComponentType(void* cbdata, int iid, std:
}
else
{
// Component type is already loaded, so do hotloading:
if (!componentManager->m_CurrentlyHotloading)
{
componentManager->m_ScriptInterface.ReportError("Registering component type with already-registered name"); // TODO: report the actual name
@ -158,10 +167,10 @@ void CComponentManager::Script_RegisterComponentType(void* cbdata, int iid, std:
}
// Construct a new ComponentType, using the wrapper's alloc functions
ComponentType ct = { CT_Script, ctWrapper.iid, ctWrapper.alloc, ctWrapper.dealloc, cname, ctor.get() };
ComponentType ct = { CT_Script, iid, ctWrapper.alloc, ctWrapper.dealloc, cname, ctor.get() };
componentManager->m_ComponentTypesById[cid] = ct;
componentManager->m_CurrentComponent = cid;
componentManager->m_CurrentComponent = cid; // needed by Subscribe
// Stop the ctor getting GCed
componentManager->m_ScriptInterface.AddRoot(&componentManager->m_ComponentTypesById[cid].ctor, "ComponentType ctor");
@ -181,15 +190,30 @@ void CComponentManager::Script_RegisterComponentType(void* cbdata, int iid, std:
for (std::vector<std::string>::const_iterator it = methods.begin(); it != methods.end(); ++it)
{
std::string name = (*it).substr(2); // strip the "On" prefix
// Handle "OnGlobalFoo" functions specially
bool isGlobal = false;
if (name.substr(0, 6) == "Global")
{
isGlobal = true;
name = name.substr(6);
}
std::map<std::string, MessageTypeId>::const_iterator mit = componentManager->m_MessageTypeIdsByName.find(name);
if (mit == componentManager->m_MessageTypeIdsByName.end())
{
componentManager->m_ScriptInterface.ReportError("Registered component has unrecognised 'On...' message handler method"); // TODO: report the actual name
return;
}
componentManager->SubscribeToMessageType(cid, mit->second);
if (isGlobal)
componentManager->SubscribeGloballyToMessageType(mit->second);
else
componentManager->SubscribeToMessageType(mit->second);
}
componentManager->m_CurrentComponent = CID__Invalid;
if (mustReloadComponents)
{
// For every script component with this cid, we need to switch its
@ -205,6 +229,30 @@ void CComponentManager::Script_RegisterComponentType(void* cbdata, int iid, std:
}
}
void CComponentManager::Script_RegisterInterface(void* cbdata, std::string name)
{
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
std::map<std::string, InterfaceId>::iterator it = componentManager->m_InterfaceIdsByName.find(name);
if (it != componentManager->m_InterfaceIdsByName.end())
{
componentManager->m_ScriptInterface.ReportError("Registering interface with already-registered name"); // TODO: report the actual name
return;
}
// IIDs start at 1, so size+1 is the next unused one
size_t id = componentManager->m_InterfaceIdsByName.size() + 1;
componentManager->m_InterfaceIdsByName[name] = id;
componentManager->m_ScriptInterface.SetGlobal(("IID_" + name).c_str(), (int )id);
}
void CComponentManager::Script_RegisterGlobal(void* cbdata, std::string name, CScriptVal value)
{
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
componentManager->m_ScriptInterface.SetGlobal(name.c_str(), value);
}
IComponent* CComponentManager::Script_QueryInterface(void* cbdata, int ent, int iid)
{
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
@ -236,6 +284,18 @@ void CComponentManager::Script_BroadcastMessage(void* cbdata, int mtid, CScriptV
delete msg;
}
int CComponentManager::Script_AddEntity(void* cbdata, std::string templateName)
{
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
std::wstring name(templateName.begin(), templateName.end());
// TODO: should validate the string to make sure it doesn't contain scary characters
// that will let it access non-component-template files
entity_id_t ent = componentManager->AddEntity(name, componentManager->AllocateNewEntity());
return (int)ent;
}
void CComponentManager::ResetState()
{
// Delete all IComponents
@ -282,12 +342,21 @@ void CComponentManager::RegisterMessageType(MessageTypeId mtid, const char* name
m_MessageTypeIdsByName[name] = mtid;
}
void CComponentManager::SubscribeToMessageType(ComponentTypeId cid, MessageTypeId mtid)
void CComponentManager::SubscribeToMessageType(MessageTypeId mtid)
{
// TODO: verify mtid
debug_assert(cid == m_CurrentComponent); // TODO: this should be redundant
std::vector<ComponentTypeId>& types = m_ComponentTypeIdsByMessageType[mtid];
types.push_back(cid);
debug_assert(m_CurrentComponent != CID__Invalid);
std::vector<ComponentTypeId>& 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
debug_assert(m_CurrentComponent != CID__Invalid);
std::vector<ComponentTypeId>& types = m_GlobalMessageSubscriptions[mtid];
types.push_back(m_CurrentComponent);
std::sort(types.begin(), types.end()); // TODO: just sort once at the end of LoadComponents
}
@ -309,6 +378,9 @@ std::string CComponentManager::LookupComponentTypeName(ComponentTypeId cid) cons
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<ComponentTypeId, ComponentType>::const_iterator it = m_ComponentTypesById.begin();
for (; it != m_ComponentTypesById.end(); ++it)
if (it->second.iid == iid && it->second.type == CT_ScriptWrapper)
@ -416,6 +488,48 @@ void CComponentManager::AddMockComponent(entity_id_t ent, InterfaceId iid, IComp
emap1.insert(std::make_pair(ent, &component));
}
entity_id_t CComponentManager::AddEntity(const std::wstring& templateName, entity_id_t ent)
{
ICmpTemplateManager *tempMan = static_cast<ICmpTemplateManager*> (QueryInterface(SYSTEM_ENTITY, IID_TemplateManager));
if (!tempMan)
{
debug_warn(L"No ICmpTemplateManager loaded");
return INVALID_ENTITY;
}
// TODO: should assert that ent doesn't exist
const CParamNode* tmpl = tempMan->LoadTemplate(ent, templateName, -1);
if (!tmpl)
return INVALID_ENTITY; // LoadTemplate will have reported the error
// 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(L"Unrecognised component type name '%hs' in entity template '%ls'", it->first.c_str(), templateName.c_str());
return INVALID_ENTITY;
}
if (!AddComponent(ent, cid, it->second))
{
LOGERROR(L"Failed to construct component type name '%hs' in entity template '%ls'", it->first.c_str(), templateName.c_str());
return INVALID_ENTITY;
}
// TODO: maybe we should delete already-constructed components if one of them fails?
}
return ent;
}
void CComponentManager::DestroyComponentsSoon(entity_id_t ent)
{
m_DestructionQueue.push_back(ent);
@ -423,10 +537,17 @@ void CComponentManager::DestroyComponentsSoon(entity_id_t ent)
void CComponentManager::FlushDestroyedComponents()
{
for (std::vector<entity_id_t>::iterator it = m_DestructionQueue.begin(); it != m_DestructionQueue.end(); ++it)
// 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<entity_id_t> queue;
queue.swap(m_DestructionQueue);
for (std::vector<entity_id_t>::iterator it = queue.begin(); it != queue.end(); ++it)
{
entity_id_t ent = *it;
PostMessage(ent, CMessageDestroy());
// Destroy the components, and remove from m_ComponentsByTypeId:
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::iterator iit = m_ComponentsByTypeId.begin();
for (; iit != m_ComponentsByTypeId.end(); ++iit)
@ -447,8 +568,6 @@ void CComponentManager::FlushDestroyedComponents()
ifcit->second.erase(ent);
}
}
m_DestructionQueue.clear();
}
IComponent* CComponentManager::QueryInterface(entity_id_t ent, InterfaceId iid) const
@ -485,46 +604,69 @@ const std::map<entity_id_t, IComponent*>& CComponentManager::GetEntitiesWithInte
void CComponentManager::PostMessage(entity_id_t ent, const CMessage& msg) const
{
std::map<MessageTypeId, std::vector<ComponentTypeId> >::const_iterator it = m_ComponentTypeIdsByMessageType.find(msg.GetType());
if (it == m_ComponentTypeIdsByMessageType.end())
// Send the message to components of ent, that subscribed locally to this message
std::map<MessageTypeId, std::vector<ComponentTypeId> >::const_iterator it;
it = m_LocalMessageSubscriptions.find(msg.GetType());
if (it != m_LocalMessageSubscriptions.end())
{
// Nobody subscribed to this message
return;
std::vector<ComponentTypeId>::const_iterator ctit = it->second.begin();
for (; ctit != it->second.end(); ++ctit)
{
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::const_iterator emap = m_ComponentsByTypeId.find(*ctit);
if (emap == m_ComponentsByTypeId.end())
continue;
std::map<entity_id_t, IComponent*>::const_iterator eit = emap->second.find(ent);
if (eit != emap->second.end())
eit->second->HandleMessage(m_SimContext, msg, false);
}
}
std::vector<ComponentTypeId>::const_iterator ctit = it->second.begin();
for (; ctit != it->second.end(); ++ctit)
{
int cid = *ctit;
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::const_iterator emap = m_ComponentsByTypeId.find(cid);
if (emap == m_ComponentsByTypeId.end())
continue;
std::map<entity_id_t, IComponent*>::const_iterator eit = emap->second.find(ent);
if (eit != emap->second.end())
eit->second->HandleMessage(m_SimContext, msg);
}
SendGlobalMessage(msg);
}
void CComponentManager::BroadcastMessage(const CMessage& msg) const
{
std::map<MessageTypeId, std::vector<ComponentTypeId> >::const_iterator it = m_ComponentTypeIdsByMessageType.find(msg.GetType());
if (it == m_ComponentTypeIdsByMessageType.end())
// Send the message to components of all entities that subscribed locally to this message
std::map<MessageTypeId, std::vector<ComponentTypeId> >::const_iterator it;
it = m_LocalMessageSubscriptions.find(msg.GetType());
if (it != m_LocalMessageSubscriptions.end())
{
// Nobody subscribed to this message
return;
std::vector<ComponentTypeId>::const_iterator ctit = it->second.begin();
for (; ctit != it->second.end(); ++ctit)
{
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::const_iterator emap = m_ComponentsByTypeId.find(*ctit);
if (emap == m_ComponentsByTypeId.end())
continue;
std::map<entity_id_t, IComponent*>::const_iterator eit = emap->second.begin();
for (; eit != emap->second.end(); ++eit)
eit->second->HandleMessage(m_SimContext, msg, false);
}
}
std::vector<ComponentTypeId>::const_iterator ctit = it->second.begin();
for (; ctit != it->second.end(); ++ctit)
{
int cid = *ctit;
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::const_iterator emap = m_ComponentsByTypeId.find(cid);
if (emap == m_ComponentsByTypeId.end())
continue;
SendGlobalMessage(msg);
}
std::map<entity_id_t, IComponent*>::const_iterator eit = emap->second.begin();
for (; eit != emap->second.end(); ++eit)
eit->second->HandleMessage(m_SimContext, msg);
void CComponentManager::SendGlobalMessage(const CMessage& msg) const
{
// (Common functionality for PostMessage and BroadcastMessage)
// Send the message to components of all entities that subscribed globally to this message
std::map<MessageTypeId, std::vector<ComponentTypeId> >::const_iterator it;
it = m_GlobalMessageSubscriptions.find(msg.GetType());
if (it != m_GlobalMessageSubscriptions.end())
{
std::vector<ComponentTypeId>::const_iterator ctit = it->second.begin();
for (; ctit != it->second.end(); ++ctit)
{
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::const_iterator emap = m_ComponentsByTypeId.find(*ctit);
if (emap == m_ComponentsByTypeId.end())
continue;
std::map<entity_id_t, IComponent*>::const_iterator eit = emap->second.begin();
for (; eit != emap->second.end(); ++eit)
eit->second->HandleMessage(m_SimContext, msg, true);
}
}
}

View File

@ -84,7 +84,8 @@ public:
void RegisterComponentType(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*);
void RegisterComponentTypeScriptWrapper(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*);
void SubscribeToMessageType(ComponentTypeId, MessageTypeId);
void SubscribeToMessageType(MessageTypeId);
void SubscribeGloballyToMessageType(MessageTypeId);
/**
* @param cname Requested component type name (not including any "CID" or "CCmp" prefix)
@ -139,6 +140,13 @@ public:
*/
IComponent* ConstructComponent(entity_id_t ent, ComponentTypeId cid);
/**
* Constructs an entity based on the given template, and adds it the world with
* entity ID @p ent. There should not be any existing components with that entity ID.
* @return ent, or INVALID_ENTITY on error
*/
entity_id_t AddEntity(const std::wstring& templateName, entity_id_t ent);
/**
* Destroys all the components belonging to the specified entity when FlushDestroyedComponents is called.
* Has no effect if the entity does not exist, or has already been added to the destruction queue.
@ -153,9 +161,20 @@ public:
void FlushDestroyedComponents();
IComponent* QueryInterface(entity_id_t ent, InterfaceId iid) const;
const std::map<entity_id_t, IComponent*>& GetEntitiesWithInterface(InterfaceId iid) const;
/**
* Send a message, targeted at a particular entity. The message will be received by any
* components of that entity which subscribed to the message type, and by any other components
* that subscribed globally to the message type.
*/
void PostMessage(entity_id_t ent, const CMessage& msg) const;
/**
* Send a message, not targeted at any particular entity. The message will be received by any
* components that subscribed (either globally or not) to the message type.
*/
void BroadcastMessage(const CMessage& msg) const;
/**
@ -177,26 +196,32 @@ public:
private:
// Implementations of functions exposed to scripts
static void Script_RegisterComponentType(void* cbdata, int iid, std::string cname, CScriptVal ctor);
static void Script_RegisterInterface(void* cbdata, std::string name);
static void Script_RegisterGlobal(void* cbdata, std::string name, CScriptVal value);
static IComponent* Script_QueryInterface(void* cbdata, int ent, int iid);
static void Script_PostMessage(void* cbdata, int ent, int mtid, CScriptVal data);
static void Script_BroadcastMessage(void* cbdata, int mtid, CScriptVal data);
static int Script_AddEntity(void* cbdata, std::string templateName);
void SendGlobalMessage(const CMessage& msg) const;
ComponentTypeId GetScriptWrapper(InterfaceId iid);
ScriptInterface m_ScriptInterface;
const CSimContext& m_SimContext;
ComponentTypeId m_CurrentComponent;
ComponentTypeId m_CurrentComponent; // used when loading component types
bool m_CurrentlyHotloading;
// TODO: some of these should be vectors
std::map<ComponentTypeId, ComponentType> m_ComponentTypesById;
std::map<InterfaceId, std::map<entity_id_t, IComponent*> > m_ComponentsByInterface;
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> > m_ComponentsByTypeId;
std::map<MessageTypeId, std::vector<ComponentTypeId> > m_ComponentTypeIdsByMessageType;
std::map<MessageTypeId, std::vector<ComponentTypeId> > m_LocalMessageSubscriptions;
std::map<MessageTypeId, std::vector<ComponentTypeId> > m_GlobalMessageSubscriptions;
std::map<std::string, ComponentTypeId> m_ComponentTypeIdsByName;
std::map<std::string, MessageTypeId> m_MessageTypeIdsByName;
std::map<std::string, InterfaceId> m_InterfaceIdsByName;
// TODO: maintaining both ComponentsBy* is nasty; can we get rid of one,
// while keeping QueryInterface and PostMessage sufficiently efficient?

View File

@ -86,9 +86,9 @@ public:
m_ComponentManager.AddMockComponent(ent, iid, component);
}
void HandleMessage(IComponent* cmp, const CMessage& msg)
void HandleMessage(IComponent* cmp, const CMessage& msg, bool global)
{
cmp->HandleMessage(m_Context, msg);
cmp->HandleMessage(m_Context, msg, global);
}
/**

View File

@ -23,7 +23,7 @@ IComponent::~IComponent()
{
}
void IComponent::HandleMessage(const CSimContext& UNUSED(context), const CMessage& UNUSED(msg))
void IComponent::HandleMessage(const CSimContext& UNUSED(context), const CMessage& UNUSED(msg), bool UNUSED(global))
{
}

View File

@ -38,7 +38,7 @@ public:
virtual void Init(const CSimContext& context, const CParamNode& paramNode) = 0;
virtual void Deinit(const CSimContext& context) = 0;
virtual void HandleMessage(const CSimContext& context, const CMessage& msg);
virtual void HandleMessage(const CSimContext& context, const CMessage& msg, bool global);
entity_id_t GetEntityId() const { return m_EntityId; }
void SetEntityId(entity_id_t ent) { m_EntityId = ent; }

View File

@ -28,6 +28,7 @@ protected:
public:
virtual EMessageTypeId GetType() const = 0;
virtual const char* GetScriptHandlerName() const = 0;
virtual const char* GetScriptGlobalHandlerName() const = 0;
virtual jsval ToJSVal(ScriptInterface&) const = 0;
};
// TODO: GetType could be replaced with a plain member variable to avoid some

View File

@ -156,6 +156,7 @@ public:
CMessageTurnStart msg1;
CMessageUpdate msg2(CFixed_23_8::FromInt(100));
CMessageInterpolate msg3(0);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent1, IID_Test1))->GetX(), 11000);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent2, IID_Test1))->GetX(), 12000);
@ -163,6 +164,7 @@ public:
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent4, IID_Test1))->GetX(), 11000);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent4, IID_Test2))->GetX(), 21000);
// Test_1A subscribed locally to msg1, nothing subscribed globally
man.PostMessage(ent1, msg1);
man.PostMessage(ent1, msg2);
@ -180,6 +182,7 @@ public:
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent4, IID_Test1))->GetX(), 11001);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent4, IID_Test2))->GetX(), 21050);
// Test_1B, Test_2A subscribed locally to msg2, nothing subscribed globally
man.BroadcastMessage(msg2);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent1, IID_Test1))->GetX(), 11002);
@ -187,6 +190,23 @@ public:
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent3, IID_Test2))->GetX(), 21150);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent4, IID_Test1))->GetX(), 11001);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent4, IID_Test2))->GetX(), 21150);
// Test_1A subscribed locally to msg3, Test_1B subscribed globally
man.BroadcastMessage(msg3);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent1, IID_Test1))->GetX(), 11004); // local
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent2, IID_Test1))->GetX(), 12030); // global
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent3, IID_Test2))->GetX(), 21150);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent4, IID_Test1))->GetX(), 11003); // local
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent4, IID_Test2))->GetX(), 21150);
man.PostMessage(ent1, msg3);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent1, IID_Test1))->GetX(), 11006); // local
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent2, IID_Test1))->GetX(), 12050); // global
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent3, IID_Test2))->GetX(), 21150);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent4, IID_Test1))->GetX(), 11003); // local - skipped
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent4, IID_Test2))->GetX(), 21150);
}
void test_ParamNode()
@ -265,6 +285,23 @@ public:
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent1, IID_Test1))->GetX(), 3);
}
void test_script_interface()
{
CSimContext context;
CComponentManager man(context);
man.LoadComponentTypes();
TS_ASSERT(man.LoadScript(L"simulation/components/interfaces/test-interface.js"));
TS_ASSERT(man.LoadScript(L"simulation/components/test-interface.js"));
entity_id_t ent1 = 1;
CParamNode noParam;
man.AddComponent(ent1, man.LookupCID("TestScript1_Interface"), noParam);
man.AddComponent(ent1, man.LookupCID("TestScript2_Interface"), noParam);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent1, IID_Test1))->GetX(), 1000 + IID__LastNative);
}
void test_script_errors()
{
CSimContext context;
@ -322,6 +359,37 @@ public:
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent2, IID_Test1))->GetX(), 21000);
}
void test_script_AddEntity()
{
CSimContext context;
CComponentManager man(context);
man.LoadComponentTypes();
TS_ASSERT(man.LoadScript(L"simulation/components/test-addentity.js"));
TS_ASSERT(man.LoadScript(L"simulation/components/addentity/test-addentity.js"));
entity_id_t ent1 = man.AllocateNewEntity();
entity_id_t ent2 = ent1 + 2;
CParamNode noParam;
TS_ASSERT(man.AddComponent(SYSTEM_ENTITY, CID_TemplateManager, noParam));
TS_ASSERT(man.AddComponent(ent1, man.LookupCID("TestScript1_AddEntity"), noParam));
TS_ASSERT(man.QueryInterface(ent2, IID_Test1) == NULL);
TS_ASSERT(man.QueryInterface(ent2, IID_Test2) == NULL);
{
TestLogger logger; // ignore bogus-template warnings
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent1, IID_Test1))->GetX(), (int)ent2);
}
TS_ASSERT(man.QueryInterface(ent2, IID_Test1) != NULL);
TS_ASSERT(man.QueryInterface(ent2, IID_Test2) != NULL);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent2, IID_Test1))->GetX(), 999);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent2, IID_Test2))->GetX(), 12345);
}
void test_script_messages()
{
CSimContext context;
@ -329,24 +397,27 @@ public:
man.LoadComponentTypes();
TS_ASSERT(man.LoadScript(L"simulation/components/test-msg.js"));
entity_id_t ent1 = 1, ent2 = 2;
entity_id_t ent1 = 1, ent2 = 2, ent3 = 3;
CParamNode noParam;
man.AddComponent(ent1, man.LookupCID("TestScript1A"), noParam);
man.AddComponent(ent1, man.LookupCID("TestScript2A"), noParam);
man.AddComponent(ent2, man.LookupCID("TestScript1A"), noParam);
man.AddComponent(ent2, CID_Test2A, noParam);
man.AddComponent(ent3, man.LookupCID("TestScript1B"), noParam);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent1, IID_Test1))->GetX(), 100);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent2, IID_Test1))->GetX(), 100);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent2, IID_Test2))->GetX(), 21000);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent2, IID_Test2))->GetX(), 21000);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent3, IID_Test1))->GetX(), 100);
// This GetX broadcasts messages
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent1, IID_Test2))->GetX(), 200);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent1, IID_Test2))->GetX(), 200);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent1, IID_Test1))->GetX(), 650);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent2, IID_Test1))->GetX(), 5150);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent2, IID_Test2))->GetX(), 26050);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent2, IID_Test2))->GetX(), 26050);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent3, IID_Test1))->GetX(), 5650);
}
void test_script_template()

View File

@ -33,6 +33,10 @@
#include "renderer/Renderer.h"
#include "simulation/LOSManager.h"
#include "simulation/Simulation.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpPlayer.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpPosition.h"
namespace
{
@ -64,6 +68,22 @@ namespace
g_Game = new CGame();
}
void AddDefaultPlayers()
{
CmpPtr<ICmpPlayerManager> cmpPlayerMan(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
debug_assert(!cmpPlayerMan.null());
// TODO: pick a sensible number, give them names and colours etc
size_t numPlayers = 4;
for (size_t i = 0; i < numPlayers; ++i)
{
entity_id_t ent = g_Game->GetSimulation2()->AddEntity(L"special/player");
cmpPlayerMan->AddPlayer(ent);
}
// Also TODO: Maybe it'd be sensible to load this from a map XML file via CMapReader,
// rather than duplicating the creation code here?
}
void StartGame()
{
PSRETURN ret = g_Game->StartGame(&g_GameAttributes);
@ -98,6 +118,8 @@ MESSAGEHANDLER(GenerateMap)
delete[] heightmap;
AddDefaultPlayers();
// Start the game, load data files - this must be done before initialising
// the terrain texture below, since the terrains must be loaded before being
// used.

View File

@ -46,6 +46,7 @@
#include "simulation/EntityManager.h"
#include "simulation/TerritoryManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpTemplateManager.h"
@ -189,6 +190,26 @@ MESSAGEHANDLER(SetSelectionPreview)
QUERYHANDLER(GetObjectSettings)
{
if (g_UseSimulation2)
{
sObjectSettings settings;
settings.player = 0;
CmpPtr<ICmpOwnership> cmpOwner (*g_Game->GetSimulation2(), msg->id);
if (!cmpOwner.null())
{
int32_t player = cmpOwner->GetOwner();
if (player != -1)
settings.player = player;
}
// TODO: selections
msg->settings = settings;
return;
}
CUnit* unit = View::GetView(msg->view)->GetUnit(msg->id);
if (! unit) return;
@ -242,15 +263,30 @@ BEGIN_COMMAND(SetObjectSettings)
void Do()
{
CUnit* unit = View::GetView(msg->view)->GetUnit(msg->id);
if (! unit) return;
sObjectSettings settings = msg->settings;
m_PlayerOld = unit->GetPlayerID();
m_PlayerNew = (size_t)settings.player;
if (g_UseSimulation2)
{
CmpPtr<ICmpOwnership> cmpOwner (*g_Game->GetSimulation2(), msg->id);
m_PlayerOld = 0;
if (!cmpOwner.null())
{
int32_t player = cmpOwner->GetOwner();
if (player != -1)
m_PlayerOld = player;
}
m_SelectionsOld = unit->GetActorSelections();
// TODO: selections
}
else
{
CUnit* unit = View::GetView(msg->view)->GetUnit(msg->id);
if (! unit) return;
m_PlayerOld = unit->GetPlayerID();
m_SelectionsOld = unit->GetActorSelections();
}
m_PlayerNew = (size_t)settings.player;
std::vector<std::wstring> selections = *settings.selections;
for (std::vector<std::wstring>::iterator it = selections.begin(); it != selections.end(); ++it)
@ -274,6 +310,16 @@ BEGIN_COMMAND(SetObjectSettings)
private:
void Set(size_t player, const std::set<CStr>& selections)
{
if (g_UseSimulation2)
{
CmpPtr<ICmpOwnership> cmpOwner (*g_Game->GetSimulation2(), msg->id);
if (!cmpOwner.null())
cmpOwner->SetOwner(player);
// TODO: selections
return;
}
CUnit* unit = View::GetView(msg->view)->GetUnit(msg->id);
if (! unit) return;
@ -356,7 +402,7 @@ MESSAGEHANDLER(ObjectPreview)
if ((*msg->id).empty())
g_PreviewEntityID = INVALID_ENTITY;
else
g_PreviewEntityID = g_Game->GetSimulation2()->AddLocalEntity(*msg->id);
g_PreviewEntityID = g_Game->GetSimulation2()->AddLocalEntity(L"preview|" + *msg->id);
g_PreviewUnitName = *msg->id;
}
@ -387,7 +433,10 @@ MESSAGEHANDLER(ObjectPreview)
}
// TODO: handle random variations somehow
// TODO: set player colour
CmpPtr<ICmpOwnership> cmpOwner (*g_Game->GetSimulation2(), g_PreviewEntityID);
if (!cmpOwner.null())
cmpOwner->SetOwner(msg->settings->player);
}
return;
@ -533,8 +582,11 @@ BEGIN_COMMAND(CreateObject)
cmpPos->SetYRotation(entity_angle_t::FromFloat(m_Angle));
}
CmpPtr<ICmpOwnership> cmpOwner (*g_Game->GetSimulation2(), m_EntityID);
if (!cmpOwner.null())
cmpOwner->SetOwner(m_Player);
// TODO: handle random variations somehow
// TODO: set player colour
return;
}
@ -905,12 +957,21 @@ END_COMMAND(RotateObject)
BEGIN_COMMAND(DeleteObject)
{
// These two values are never both non-NULL
// Old simulation: These two values are never both non-NULL
std::auto_ptr<SimState::Entity> m_FrozenEntity;
std::auto_ptr<SimState::Nonentity> m_FrozenNonentity;
// New simulation:
// Saved copy of the important aspects of a unit, to allow undo
entity_id_t m_EntityID;
std::wstring m_TemplateName;
int32_t m_Owner;
CFixedVector3D m_Pos;
CFixedVector3D m_Rot;
// TODO: random selections
cDeleteObject()
: m_FrozenEntity(NULL), m_FrozenNonentity(NULL)
: m_FrozenEntity(NULL), m_FrozenNonentity(NULL), m_EntityID(INVALID_ENTITY), m_Owner(-1)
{
}
@ -921,6 +982,31 @@ BEGIN_COMMAND(DeleteObject)
void Redo()
{
if (g_UseSimulation2)
{
CSimulation2& sim = *g_Game->GetSimulation2();
CmpPtr<ICmpTemplateManager> cmpTemplateManager(sim, SYSTEM_ENTITY);
debug_assert(!cmpTemplateManager.null());
m_EntityID = msg->id;
m_TemplateName = cmpTemplateManager->GetCurrentTemplateName(m_EntityID);
CmpPtr<ICmpOwnership> cmpOwner(sim, m_EntityID);
if (!cmpOwner.null())
m_Owner = cmpOwner->GetOwner();
CmpPtr<ICmpPosition> cmpPosition(sim, m_EntityID);
if (!cmpPosition.null())
{
m_Pos = cmpPosition->GetPosition();
m_Rot = cmpPosition->GetRotation();
}
g_Game->GetSimulation2()->DestroyEntity(m_EntityID);
return;
}
CUnit* unit = GetUnitManager().FindByID(msg->id);
if (! unit) return;
@ -943,6 +1029,30 @@ BEGIN_COMMAND(DeleteObject)
void Undo()
{
if (g_UseSimulation2)
{
CSimulation2& sim = *g_Game->GetSimulation2();
entity_id_t ent = sim.AddEntity(m_TemplateName, m_EntityID);
if (ent == INVALID_ENTITY)
LOGERROR(L"Failed to load entity template '%ls'", m_TemplateName.c_str());
else
{
CmpPtr<ICmpPosition> cmpPosition(sim, m_EntityID);
if (!cmpPosition.null())
{
cmpPosition->JumpTo(m_Pos.X, m_Pos.Z);
cmpPosition->SetXZRotation(m_Rot.X, m_Rot.Z);
cmpPosition->SetYRotation(m_Rot.Y);
}
CmpPtr<ICmpOwnership> cmpOwner(sim, m_EntityID);
if (!cmpOwner.null())
cmpOwner->SetOwner(m_Owner);
}
return;
}
if (m_FrozenEntity.get())
{
CEntity* entity = m_FrozenEntity->Thaw();

View File

@ -188,10 +188,6 @@ void ViewGame::Update(float frameLength)
}
}
// Clean up any entities destroyed during simulation update
if (g_UseSimulation2)
g_Game->GetSimulation2()->FlushDestroyedEntities();
// Interpolate the graphics - we only want to do this once per visual frame,
// not in every call to g_Game->Update
g_Game->GetSimulation()->Interpolate(actualFrameLength);