#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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& 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; tsecond.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; dname() << " 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