#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <Eris/Entity.h>
#include <Eris/Connection.h>
#include <Eris/TypeInfo.h>
#include <Eris/LogStream.h>
#include <Eris/EntityRouter.h>
#include <Eris/View.h>
#include <Eris/Exceptions.h>
#include <Eris/Avatar.h>
#include <Eris/Alarm.h>
#include <Eris/Task.h>
#include <wfmath/atlasconv.h>
#include <Atlas/Objects/Entity.h>
#include <Atlas/Objects/Operation.h>
#include <Atlas/Objects/BaseObject.h>
#include <algorithm>
#include <set>
#include <cmath>
#include <cassert>
using namespace Atlas::Objects::Operation;
using Atlas::Objects::Root;
using Atlas::Objects::Entity::RootEntity;
using Atlas::Message::Element;
using Atlas::Message::ListType;
using Atlas::Message::MapType;
using Atlas::Objects::smart_static_cast;
using Atlas::Objects::smart_dynamic_cast;
using WFMath::TimeStamp;
using WFMath::TimeDiff;
namespace Eris {
Entity::Entity(const std::string& id, TypeInfo* ty, View* vw) :
m_type(ty),
m_location(NULL),
m_id(id),
m_stamp(-1.0),
m_visible(false),
m_limbo(false),
m_updateLevel(0),
m_view(vw),
m_hasBBox(false),
m_moving(false),
m_recentlyCreated(false),
m_initialised(true)
{
assert(m_id.size() > 0);
m_orientation.identity();
m_router = new EntityRouter(this);
m_view->getConnection()->registerRouterForFrom(m_router, m_id);
}
Entity::~Entity()
{
assert(m_initialised == false);
}
void Entity::shutdown()
{
BeingDeleted.emit();
if (m_moving) m_view->removeFromPrediction(this);
while (!m_contents.empty()) {
Entity *e = m_contents.back();
e->shutdown();
delete e;
}
setLocation(NULL);
m_view->getConnection()->unregisterRouterForFrom(m_router, m_id);
m_view->entityDeleted(this); // remove ourselves from the View's content map
delete m_router;
m_initialised = false;
}
void Entity::init(const RootEntity& ge, bool fromCreateOp)
{
// setup initial state
sight(ge);
if (fromCreateOp)
{
m_recentlyCreated = true;
new Alarm(5000, sigc::mem_fun(this, &Entity::createAlarmExpired));
}
}
#pragma mark -
const Element& Entity::valueOfAttr(const std::string& attr) const
{
AttrMap::const_iterator A = m_attrs.find(attr);
if (A == m_attrs.end())
{
error() << "did getAttr(" << attr << ") on entity " << m_id << " which has no such attr";
throw InvalidOperation("no such attribute " + attr);
} else
return A->second;
}
bool Entity::hasAttr(const std::string& attr) const
{
return m_attrs.count(attr) > 0;
}
SigC::Connection Entity::observe(const std::string& attr, const AttrChangedSlot& slot)
{
// sometimes, I realize how great SigC++ is
return m_observers[attr].connect(slot);
}
WFMath::Point<3> Entity::getViewPosition() const
{
WFMath::Point<3> vpos(0.0, 0.0, 0.0);
for (const Entity* e = this; e; e = e->getLocation())
vpos = e->toLocationCoords(vpos);
return vpos;
}
WFMath::Quaternion Entity::getViewOrientation() const
{
WFMath::Quaternion vor;
vor.identity();
for (const Entity* e = this; e; e = e->getLocation())
vor *= e->getOrientation();
return vor;
}
WFMath::Point<3> Entity::getPredictedPos() const
{
WFMath::Point<3> res = (m_moving ? m_predicted.position : m_position);
// if (!res.isValid())
// {
// debug() << "invalid pos for entity " << m_id << ":" << m_name;
// }
return res;
}
WFMath::Vector<3> Entity::getPredictedVelocity() const
{
return (m_moving ? m_predicted.velocity : WFMath::Vector<3>());
}
bool Entity::isMoving() const
{
return m_moving;
}
void Entity::updatePredictedState(const WFMath::TimeStamp& t)
{
assert(isMoving());
double dt = (t - m_lastMoveTime).milliseconds() / 1000.0;
if (m_acc.isValid()) {
m_predicted.velocity = m_velocity + (m_acc * dt);
m_predicted.position = m_position + (m_velocity * dt) + (m_acc * 0.5 * dt * dt);
} else {
m_predicted.velocity = m_velocity;
m_predicted.position = m_position + (m_velocity * dt);
}
}
TypeInfoArray Entity::getUseOperations() const
{
AttrMap::const_iterator it = m_attrs.find("operations");
if (it == m_attrs.end()) return TypeInfoArray();
if (!it->second.isList()) {
warning() << "entity " << m_id << " has operations attr which is not a list";
return TypeInfoArray();
}
const ListType& opsl(it->second.asList());
TypeInfoArray useOps;
useOps.reserve(opsl.size());
TypeService* ts = m_view->getAvatar()->getConnection()->getTypeService();
for (ListType::const_iterator i=opsl.begin(); i!=opsl.end(); ++i)
{
if (!i->isString())
{
warning() << "ignoring malformed operations list item";
continue;
}
useOps.push_back(ts->getTypeByName(i->asString()));
}
return useOps;
}
#pragma mark -
void Entity::sight(const RootEntity &ge)
{
if (!ge->isDefaultLoc()) setLocationFromAtlas(ge->getLoc());
setContentsFromAtlas(ge->getContains());
setFromRoot(ge, true);
}
void Entity::setFromRoot(const Root& obj, bool allowMove)
{
beginUpdate();
Atlas::Message::MapType attrs;
obj->addToMessage(attrs);
Atlas::Message::MapType::iterator A;
attrs.erase("loc");
attrs.erase("id");
attrs.erase("contains");
if (!allowMove) filterMoveAttrs(attrs);
for (A = attrs.begin(); A != attrs.end(); ++A) {
// see if the value in the sight matches the exsiting value
AttrMap::iterator I = m_attrs.find(A->first);
if ((I != m_attrs.end()) && (I->second == A->second)) continue;
setAttr(A->first, A->second);
}
endUpdate();
}
void Entity::filterMoveAttrs(Atlas::Message::MapType& attrs) const
{
attrs.erase("pos");
attrs.erase("mode");
attrs.erase("velocity");
attrs.erase("orientation");
attrs.erase("accel");
}
void Entity::onTalk(const Atlas::Objects::Operation::RootOperation& talk)
{
const std::vector<Root>& talkArgs = talk->getArgs();
if (talkArgs.empty())
{
warning() << "entity " << getId() << " got sound(talk) with no args";
return;
}
Say.emit(talkArgs.front());
Noise.emit(talk);
m_view->getAvatar()->Hear.emit(this, talk);
}
void Entity::onLocationChanged(Entity* oldLoc)
{
LocationChanged.emit(oldLoc);
}
void Entity::onMoved()
{
Moved.emit();
}
void Entity::onAction(const Atlas::Objects::Operation::RootOperation& arg)
{
Acted.emit(arg);
}
void Entity::onSoundAction(const Atlas::Objects::Operation::RootOperation& op)
{
Noise.emit(op);
m_view->getAvatar()->Hear.emit(this, op);
}
void Entity::onImaginary(const Atlas::Objects::Root& arg)
{
if (arg->hasAttr("description")) {
Emote.emit(arg->getAttr("description").asString());
}
}
void Entity::setMoving(bool inMotion)
{
assert(m_moving != inMotion);
if (m_moving) m_view->removeFromPrediction(this);
m_moving = inMotion;
if (m_moving) {
m_predicted.position = m_position;
m_predicted.velocity = m_velocity;
m_view->addToPrediction(this);
}
Moving.emit(inMotion);
}
void Entity::onChildAdded(Entity* child)
{
ChildAdded.emit(child);
}
void Entity::onChildRemoved(Entity* child)
{
ChildRemoved(child);
}
#pragma mark -
void Entity::setAttr(const std::string &attr, const Element &val)
{
beginUpdate();
Element& target = m_attrs[attr];
mergeOrCopyElement(val, target);
nativeAttrChanged(attr, target);
onAttrChanged(attr, target);
// fire observers
ObserverMap::const_iterator obs = m_observers.find(attr);
if (obs != m_observers.end()) obs->second.emit(attr, target);
addToUpdate(attr);
endUpdate();
}
bool Entity::nativeAttrChanged(const std::string& attr, const Element& v)
{
// in the future, hash these names to a compile-time integer index, and
// make this a switch statement. The same indice could also be used
// in endUpdate
if (attr == "name") {
m_name = v.asString();
return true;
} else if (attr == "stamp") {
m_stamp = v.asFloat();
return true;
} else if (attr == "pos") {
m_position.fromAtlas(v);
return true;
} else if (attr == "velocity") {
m_velocity.fromAtlas(v);
return true;
} else if (attr == "accel") {
m_acc.fromAtlas(v);
return true;
} else if (attr == "orientation") {
m_orientation.fromAtlas(v);
return true;
} else if (attr == "description") {
m_description = v.asString();
return true;
} else if (attr == "bbox") {
m_bbox.fromAtlas(v);
m_hasBBox = true;
return true;
} else if ((attr == "loc") ||(attr == "contains")) {
throw InvalidOperation("tried to set loc or contains via setProperty");
} else if (attr == "tasks") {
updateTasks(v);
return true;
}
return false; // not a native property
}
void Entity::onAttrChanged(const std::string&, const Element&)
{
// no-op by default
}
void Entity::beginUpdate()
{
++m_updateLevel;
}
void Entity::addToUpdate(const std::string& attr)
{
assert(m_updateLevel > 0);
m_modifiedAttrs.insert(attr);
}
void Entity::endUpdate()
{
if (m_updateLevel < 1)
{
error() << "mismatched begin/end update pair on entity";
return;
}
if (--m_updateLevel == 0) // unlocking updates
{
Changed.emit(m_modifiedAttrs);
if (m_modifiedAttrs.count("pos") ||
m_modifiedAttrs.count("velocity") ||
m_modifiedAttrs.count("orientation"))
{
m_lastMoveTime = TimeStamp::now();
const WFMath::Vector<3> & velocity = getVelocity();
bool nowMoving = (velocity.isValid() && (velocity.sqrMag() > 1e-3));
if (nowMoving != m_moving) setMoving(nowMoving);
onMoved();
}
m_modifiedAttrs.clear();
}
}
static int findTaskByName(const TaskArray& a, const std::string& nm)
{
for (unsigned int t=0; t < a.size(); ++t)
{
if (a[t]->name() == nm) return t;
}
return -1;
}
void Entity::updateTasks(const Element& e)
{
if (!e.isList()) return; // malformed
const ListType& taskList(e.asList());
/*
Explanation:
We take a copy of the current pointer-to-task array, clear out the 'real'
task array, and then walk the array defined by Atlas. For each pre-existing
task still specified in the Atlas data, we find the old task in the copied
array, re-add it to the main list, and NULL the entry in the copied list.
At the end of iterating the Atlas data, any pointers still valid in the
copied list are dead, and can be deleted.
*/
TaskArray previousTasks(m_tasks);
m_tasks.clear();
for (unsigned int t=0; t<taskList.size(); ++t)
{
const MapType& tkmap(taskList[t].asMap());
MapType::const_iterator it = tkmap.find("name");
if (it == tkmap.end())
{
error() << "task without name";
continue;
}
int index = findTaskByName(previousTasks, it->second.asString());
Task *t;
if (index < 0)
{ // not found, create a new one
t = new Task(this, it->second.asString());
TaskAdded.emit(t);
} else {
t = previousTasks[index];
previousTasks[index] = NULL;
}
m_tasks.push_back(t);
t->updateFromAtlas(tkmap);
} // of Atlas-specified tasks iteration
for (unsigned int d=0; d<previousTasks.size(); ++d)
{
if (!previousTasks[d]) continue;
TaskRemoved(previousTasks[d]);
delete previousTasks[d];
} // of previous-task cleanup iteration
}
void Entity::removeTask(Task* t)
{
TaskArray::iterator it = std::find(m_tasks.begin(), m_tasks.end(), t);
if (it == m_tasks.end())
{
error() << "unknown task " << t->name() << " on entity " << this;
return;
}
m_tasks.erase(it);
TaskRemoved(t);
}
#pragma mark -
void Entity::setLocationFromAtlas(const std::string& locId)
{
if (locId.empty()) return;
Entity* newLocation = m_view->getEntity(locId);
if (!newLocation)
{
m_view->getEntityFromServer(locId);
m_limbo = true;
setVisible(false); // fire disappearance, VisChanged if necessary
if (m_location) removeFromLocation();
m_location = NULL;
assert(m_visible == false);
return;
}
setLocation(newLocation);
}
void Entity::setLocation(Entity* newLocation)
{
if (newLocation == m_location) return;
// do the actual member updating
bool wasVisible = isVisible();
if (m_location) removeFromLocation();
Entity* oldLocation = m_location;
m_location = newLocation;
onLocationChanged(oldLocation);
// fire VisChanged and Appearance/Disappearance signals
updateCalculatedVisibility(wasVisible);
if (m_location) addToLocation();
}
void Entity::addToLocation()
{
assert(!m_location->hasChild(m_id));
m_location->addChild(this);
}
void Entity::removeFromLocation()
{
assert(m_location->hasChild(m_id));
m_location->removeChild(this);
}
#pragma mark -
void Entity::buildEntityDictFromContents(IdEntityMap& dict)
{
for (unsigned int C=0; C < m_contents.size(); ++C) {
Entity* child = m_contents[C];
dict[child->getId()] = child;
}
}
void Entity::setContentsFromAtlas(const StringList& contents)
{
// convert existing contents into a map, for fast membership tests
IdEntityMap oldContents;
buildEntityDictFromContents(oldContents);
// iterate over new contents
for (StringList::const_iterator I=contents.begin(); I != contents.end(); ++I) {
Entity* child = NULL;
IdEntityMap::iterator J = oldContents.find(*I);
if (J != oldContents.end()) {
child = J->second;
assert(child->getLocation() == this);
oldContents.erase(J);
} else {
child = m_view->getEntity(*I);
if (!child) {
// we don't have the entity at all, so request it and skip
// processing it here; everything will come right when it
// arrives.
m_view->getEntityFromServer(*I);
continue;
}
if (child->m_limbo) {
assert(child->m_visible == false);
child->m_limbo = false;
} else if (child->isVisible()) {
// server has gone mad, it has a location, and it's visible
error() << "got set of contents, specifying child " << child
<< " which is currently visible in another location";
continue;
}
/* we have found the child, update it's location */
child->setLocation(this);
}
child->setVisible(true);
} // of contents list iteration
// mark previous contents which are not in new contents as invisible
for (IdEntityMap::const_iterator J = oldContents.begin(); J != oldContents.end(); ++J) {
J->second->setVisible(false);
}
}
bool Entity::hasChild(const std::string& eid) const
{
for (EntityArray::const_iterator C=m_contents.begin(); C != m_contents.end(); ++C) {
if ((*C)->getId() == eid) return true;
}
return false;
}
void Entity::addChild(Entity* e)
{
m_contents.push_back(e);
onChildAdded(e);
assert(e->getLocation() == this);
}
void Entity::removeChild(Entity* e)
{
assert(e->getLocation() == this);
for (EntityArray::iterator C=m_contents.begin(); C != m_contents.end(); ++C)
{
if (*C == e)
{
m_contents.erase(C);
onChildRemoved(e);
return;
}
}
error() << "child " << e->getId() << " of entity " << m_id << " not found doing remove";
}
#pragma mark -
// visiblity related methods
void Entity::setVisible(bool vis)
{
// force visibility to false if in limbo; necessary for the character entity,
// which otherwise gets double appearances on activation
if (m_limbo) vis = false;
bool wasVisible = isVisible(); // store before we update m_visible
m_visible = vis;
updateCalculatedVisibility(wasVisible);
}
bool Entity::isVisible() const
{
if (m_limbo) return false;
if (m_location)
return m_visible && m_location->isVisible();
else
return m_visible; // only for the root entity
}
void Entity::updateCalculatedVisibility(bool wasVisible)
{
bool nowVisible = isVisible();
if (nowVisible == wasVisible) return;
/* the following code looks odd, so remember that only one of nowVisible and
wasVisible can ever be true. The structure is necesary so that we fire
Appearances top-down, but Disappearances bottom-up. */
if (nowVisible) {
m_view->setEntityVisible(this, true);
onVisibilityChanged(true);
}
for (unsigned int C=0; C < m_contents.size(); ++C)
{
/* in case this isn't clear; if we were visible, then child visibility
was simply it's locally set value; if we were invisible, that the
child must also have been invisible too. */
bool childWasVisible = wasVisible ? m_contents[C]->m_visible : false;
m_contents[C]->updateCalculatedVisibility(childWasVisible);
}
if (wasVisible) {
m_view->setEntityVisible(this, false);
onVisibilityChanged(false);
}
}
void Entity::onVisibilityChanged(bool vis)
{
VisibilityChanged.emit(vis);
}
void Entity::createAlarmExpired()
{
m_recentlyCreated = false;
}
} // of namespace
syntax highlighted by Code2HTML, v. 0.9.1