#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <Eris/View.h>
#include <Eris/Entity.h>
#include <Eris/LogStream.h>
#include <Eris/Connection.h>
#include <Eris/Exceptions.h>
#include <Eris/Avatar.h>
#include <Eris/Factory.h>
#include <Eris/TypeService.h>
#include <Eris/TypeInfo.h>
#include <Eris/Task.h>
#include <Atlas/Objects/Entity.h>
#include <Atlas/Objects/Operation.h>
using namespace Atlas::Objects::Operation;
using Atlas::Objects::Root;
using Atlas::Objects::Entity::RootEntity;
using Atlas::Objects::smart_dynamic_cast;
namespace Eris
{
View::View(Avatar* av) :
m_owner(av),
m_topLevel(NULL),
m_maxPendingCount(10)
{
}
View::~View()
{
if (m_topLevel) {
m_topLevel->shutdown();
delete m_topLevel;
if (!m_contents.empty()) {
warning() << "top level entity is not empty on view destruction";
}
}
// note that errors that occurr very early during world entry, may
// cause a view to be deleted with no top-level entity; in that case we
// leak a few entities here.
for (FactoryStore::iterator F=m_factories.begin(); F != m_factories.end(); ++F) {
delete *F;
}
}
Entity* View::getEntity(const std::string& eid) const
{
IdEntityMap::const_iterator E = m_contents.find(eid);
if (E == m_contents.end()) return NULL;
return E->second;
}
void View::setEntityVisible(Entity* ent, bool vis)
{
assert(ent);
if (vis) {
Appearance.emit(ent);
} else {
Disappearance.emit(ent);
}
}
void View::registerFactory(Factory* f)
{
m_factories.insert(f);
}
sigc::connection View::notifyWhenEntitySeen(const std::string& eid, const EntitySightSlot& slot)
{
if (m_contents.count(eid)) {
error() << "notifyWhenEntitySeen: entity " << eid << " already in View";
return sigc::connection();
}
sigc::connection c = m_notifySights[eid].connect(slot);
getEntityFromServer(eid);
return c;
}
#pragma mark -
void View::update()
{
WFMath::TimeStamp t(WFMath::TimeStamp::now());
// run motion prediction for each moving entity
for (EntitySet::iterator it=m_moving.begin(); it != m_moving.end(); ++it)
(*it)->updatePredictedState(t);
typedef std::set<Task*> TaskSet;
// for first call to update, dt will be zero.
if (!m_lastUpdateTime.isValid()) m_lastUpdateTime = t;
WFMath::TimeDiff dt = t - m_lastUpdateTime;
for (TaskSet::iterator it=m_progressingTasks.begin(); it != m_progressingTasks.end(); ++it)
{
(*it)->updatePredictedProgress(dt);
}
m_lastUpdateTime = t;
}
void View::addToPrediction(Entity* ent)
{
assert(ent->isMoving());
assert(m_moving.count(ent) == 0);
m_moving.insert(ent);
}
void View::removeFromPrediction(Entity* ent)
{
assert(ent->isMoving());
assert(m_moving.count(ent) == 1);
m_moving.erase(ent);
}
void View::taskRateChanged(Task* t)
{
if (t->m_progressRate > 0.0)
{
m_progressingTasks.insert(t);
} else {
m_progressingTasks.erase(t);
}
}
#pragma mark -
// Atlas operation handlers
void View::appear(const std::string& eid, float stamp)
{
Entity* ent = getEntity(eid);
if (!ent) {
getEntityFromServer(eid);
return; // everything else will be done once the SIGHT arrives
}
if (ent->m_recentlyCreated)
{
EntityCreated.emit(ent);
ent->m_recentlyCreated = false;
}
if (ent->isVisible()) return;
if ((stamp == 0) || (stamp > ent->getStamp())) {
if (isPending(eid)) {
m_pending[eid] = SACTION_APPEAR;
} else {
// local data is out of data, re-look
getEntityFromServer(eid);
}
} else
ent->setVisible(true);
}
void View::disappear(const std::string& eid)
{
Entity* ent = getEntity(eid);
if (ent) {
ent->setVisible(false); // will ultimately cause disapeparances
} else {
if (isPending(eid)) {
//debug() << "got disappearance for pending " << eid;
m_pending[eid] = SACTION_HIDE;
} else
warning() << "got disappear for unknown entity " << eid;
}
}
void View::sight(const RootEntity& gent)
{
bool visible = true;
std::string eid = gent->getId();
PendingSightMap::iterator pending = m_pending.find(eid);
// examine the pending map, to see what we should do with this entity
if (pending != m_pending.end()) {
switch (pending->second)
{
case SACTION_APPEAR:
visible = true;
break;
case SACTION_DISCARD:
m_pending.erase(pending);
issueQueuedLook();
return;
case SACTION_HIDE:
visible = false;
break;
case SACTION_QUEUED:
error() << "got sight of queued entity " << eid << " somehow";
eraseFromLookQueue(eid);
break;
default:
throw InvalidOperation("got bad pending action for entity");
}
m_pending.erase(pending);
}
// if we got this far, go ahead and build / update it
Entity *ent = getEntity(eid);
if (ent) {
// existing entity, update in place
ent->sight(gent);
} else {
ent = initialSight(gent);
EntitySeen.emit(ent);
}
if (gent->isDefaultLoc()) { // new top level entity
setTopLevelEntity(ent);
}
ent->setVisible(visible);
issueQueuedLook();
}
Entity* View::initialSight(const RootEntity& gent)
{
Entity* ent = createEntity(gent);
assert(m_contents.count(gent->getId()) == 0);
m_contents[gent->getId()] = ent;
ent->init(gent, false);
InitialSightEntity.emit(ent);
NotifySightMap::iterator it = m_notifySights.find(gent->getId());
if (it != m_notifySights.end()) {
it->second.emit(ent);
m_notifySights.erase(it);
}
return ent;
}
void View::create(const RootEntity& gent)
{
std::string eid(gent->getId());
if (m_contents.count(eid))
{
// already known locally, just emit the signal
EntityCreated.emit( m_contents[eid] );
return;
}
bool alreadyAppeared = false;
PendingSightMap::iterator pending = m_pending.find(eid);
if (pending != m_pending.end())
{
// already being retrieved, but we have the data now
alreadyAppeared = (pending->second == SACTION_QUEUED) ||
(pending->second == SACTION_APPEAR);
pending->second = SACTION_DISCARD; // when the SIGHT turns up
}
Entity* ent = createEntity(gent);
m_contents[eid] = ent;
ent->init(gent, true);
if (gent->isDefaultLoc()) setTopLevelEntity(ent);
InitialSightEntity.emit(ent);
// depends on relative order that sight(create) and appear are recieved in
if (alreadyAppeared)
{
ent->setVisible(true);
EntityCreated.emit(ent);
}
}
void View::deleteEntity(const std::string& eid)
{
Entity* ent = getEntity(eid);
if (ent) {
// copy the child array, since setLocation will modify it
EntityArray contents;
for (unsigned int c=0; c < ent->numContained(); ++c) {
contents.push_back(ent->getContained(c));
}
while (!contents.empty()) {
Entity* child = contents.back();
child->setLocation(ent->getLocation());
WFMath::Point<3> newPos = ent->toLocationCoords(child->getPosition());
WFMath::Quaternion newOrient = ent->getOrientation() * child->getOrientation();
child->m_position = newPos;
child->m_orientation = newOrient;
contents.pop_back();
}
// force a disappear if one hasn't already happened
ent->setVisible(false); // redundant?
EntityDeleted.emit(ent);
ent->shutdown();
delete ent; // actually kill it off
} else {
if (isPending(eid)) {
//debug() << "got delete for pending entity, argh";
m_pending[eid] = SACTION_DISCARD;
} else
warning() << "got delete for unknown entity " << eid;
}
}
Entity* View::createEntity(const RootEntity& gent)
{
TypeInfo* type = getConnection()->getTypeService()->getTypeForAtlas(gent);
assert(type->isBound());
FactoryStore::const_iterator F = m_factories.begin();
for (; F != m_factories.end(); ++F) {
if ((*F)->accept(gent, type)) {
return (*F)->instantiate(gent, type, this);
}
}
return new Eris::Entity(gent->getId(), type, this);
}
void View::unseen(const std::string& eid)
{
Entity* ent = getEntity(eid);
if (!ent) return; // unseen for non-local, ignore
ent->shutdown();
delete ent; // is that all?
}
#pragma mark -
bool View::isPending(const std::string& eid) const
{
return m_pending.count(eid);
}
Connection* View::getConnection() const
{
return m_owner->getConnection();
}
void View::getEntityFromServer(const std::string& eid)
{
if (isPending(eid)) return;
// don't apply pending queue cap for anoynmous LOOKs
if (!eid.empty() && (m_pending.size() >= m_maxPendingCount)) {
m_lookQueue.push_back(eid);
m_pending[eid] = SACTION_QUEUED;
return;
}
sendLookAt(eid);
}
void View::sendLookAt(const std::string& eid)
{
Look look;
if (!eid.empty()) {
PendingSightMap::iterator pending = m_pending.find(eid);
if (pending != m_pending.end()) {
switch (pending->second)
{
case SACTION_QUEUED:
// flip over to default (APPEAR) as normal
pending->second = SACTION_APPEAR; break;
case SACTION_DISCARD:
case SACTION_HIDE:
if (m_notifySights.count(eid) == 0) {
// no-one cares, don't bother to look
m_pending.erase(pending);
issueQueuedLook();
return;
} // else someone <em>does</em> care, so let's do the look, but
// keep SightAction unchanged so it discards / is hidden as
// expected.
break;
case SACTION_APPEAR:
// this can happen if a queued entity disappears and then
// re-appears, all while in the look queue. we can safely fall
// through.
break;
default:
// broken state handling logic
assert(false);
}
} else {
// no previous entry, default to APPEAR
m_pending.insert(pending, std::make_pair(eid, SACTION_APPEAR));
}
// pending map is in the right state, build up the args now
Root what;
what->setId(eid);
look->setArgs1(what);
}
look->setFrom(m_owner->getId());
getConnection()->send(look);
}
void View::setTopLevelEntity(Entity* newTopLevel)
{
if (m_topLevel) {
if (newTopLevel == m_topLevel) return; // no change!
if (m_topLevel->isVisible() && (m_topLevel->getLocation() == NULL))
error() << "old top-level entity is visible, but has no location";
}
assert(newTopLevel->getLocation() == NULL);
m_topLevel = newTopLevel;
TopLevelEntityChanged.emit(); // fire the signal
}
void View::entityDeleted(Entity* ent)
{
assert(m_contents.count(ent->getId()));
m_contents.erase(ent->getId());
}
void View::issueQueuedLook()
{
if (m_lookQueue.empty()) return;
std::string eid = m_lookQueue.front();
m_lookQueue.pop_front();
sendLookAt(eid);
}
void View::dumpLookQueue()
{
debug() << "look queue:";
for (unsigned int i=0; i < m_lookQueue.size(); ++i) {
debug() << "\t" << m_lookQueue[i];
}
}
void View::eraseFromLookQueue(const std::string& eid)
{
std::deque<std::string>::iterator it;
for (it = m_lookQueue.begin(); it != m_lookQueue.end(); ++it) {
if (*it == eid) {
m_lookQueue.erase(it);
return;
}
}
error() << "entity " << eid << " not present in the look queue";
}
} // of namespace Eris
syntax highlighted by Code2HTML, v. 0.9.1