eris  1.4.0
A WorldForge client library.
View.cpp
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 
5 #include "View.h"
6 #include "ViewEntity.h"
7 #include "LogStream.h"
8 #include "Connection.h"
9 #include "Exceptions.h"
10 #include "Avatar.h"
11 #include "Factory.h"
12 #include "TypeService.h"
13 #include "TypeInfo.h"
14 #include "Task.h"
15 #include "EntityRouter.h"
16 
17 #include <Atlas/Objects/Entity.h>
18 #include <Atlas/Objects/Operation.h>
19 
20 using namespace Atlas::Objects::Operation;
21 using Atlas::Objects::Root;
22 using Atlas::Objects::Entity::RootEntity;
23 using Atlas::Objects::smart_dynamic_cast;
24 
25 namespace Eris {
26 
27 View::View(Avatar& av) :
28  m_owner(av),
29  m_topLevel(nullptr),
30  m_simulationSpeed(1.0),
31  m_maxPendingCount(10) {
32 }
33 
34 View::~View() {
35  if (m_topLevel) {
36  deleteEntity(m_topLevel->getId());
37  }
38 
39  //To avoid having callbacks into the View when deleting children we first move all of them to a temporary copy
40  //and then destroy that.
41  auto contents = std::move(m_contents);
42  contents.clear();
43 }
44 
45 ViewEntity* View::getEntity(const std::string& eid) const {
46  auto E = m_contents.find(eid);
47  if (E == m_contents.end()) {
48  return nullptr;
49  }
50 
51  return E->second.entity.get();
52 }
53 
54 void View::registerFactory(std::unique_ptr<Factory> f) {
55  m_factories.insert(std::move(f));
56 }
57 
58 sigc::connection View::notifyWhenEntitySeen(const std::string& eid, const EntitySightSlot& slot) {
59  if (m_contents.count(eid)) {
60  error() << "notifyWhenEntitySeen: entity " << eid << " already in View";
61  return {};
62  }
63 
64  sigc::connection c = m_notifySights[eid].connect(slot);
65  getEntityFromServer(eid);
66  return c;
67 }
68 
70  return m_owner.getConnection().getTypeService();
71 }
72 
74  return m_owner.getConnection().getTypeService();
75 }
76 
78  return m_owner.getConnection().getEventService();
79 }
80 
82  return m_owner.getConnection().getEventService();
83 }
84 
85 double View::getSimulationSpeed() const {
86  return m_simulationSpeed;
87 }
88 
89 void View::update() {
90 
91  auto pruned = pruneAbandonedPendingEntities();
92  for (size_t i = 0; i < pruned; ++i) {
93  issueQueuedLook();
94  }
95 
96  WFMath::TimeStamp t(WFMath::TimeStamp::now());
97 
98  // run motion prediction for each moving entity
99  for (auto& it : m_moving) {
100  it->updatePredictedState(t, m_simulationSpeed);
101  }
102 
103  // for first call to update, dt will be zero.
104  if (!m_lastUpdateTime.isValid()) {
105  m_lastUpdateTime = t;
106  }
107  WFMath::TimeDiff dt = t - m_lastUpdateTime;
108 
109  for (auto& m_progressingTask : m_progressingTasks) {
110  m_progressingTask->updatePredictedProgress(dt);
111  }
112 
113  m_lastUpdateTime = t;
114 
115  if (m_owner.getEntity()) {
116  auto topEntity = m_owner.getEntity()->getTopEntity();
117  setTopLevelEntity(topEntity);
118  } else {
119  setTopLevelEntity(nullptr);
120  }
121 }
122 
123 void View::addToPrediction(ViewEntity* ent) {
124  assert(ent->isMoving());
125  assert(m_moving.count(ent) == 0);
126  m_moving.insert(ent);
127 }
128 
129 void View::removeFromPrediction(ViewEntity* ent) {
130  assert(m_moving.count(ent) == 1);
131  m_moving.erase(ent);
132 }
133 
135  if (t->m_progressRate > 0.0) {
136  m_progressingTasks.insert(t);
137  } else {
138  m_progressingTasks.erase(t);
139  }
140 }
141 
142 // Atlas operation handlers
143 
144 void View::appear(const std::string& eid, double stamp) {
145  auto* ent = getEntity(eid);
146  if (!ent) {
147  getEntityFromServer(eid);
148  return; // everything else will be done once the SIGHT arrives
149  }
150 
151  if (ent->m_recentlyCreated) {
152  EntityCreated.emit(ent);
153  ent->m_recentlyCreated = false;
154  }
155 
156  if (ent->isVisible()) return;
157 
158  if ((stamp == 0) || (stamp > ent->getStamp())) {
159  if (isPending(eid)) {
160  m_pending[eid].sightAction = SightAction::APPEAR;
161  } else {
162  // local data is out of data, re-look
163  getEntityFromServer(eid);
164  }
165  } else {
166  ent->setVisible(true);
167  }
168 
169 }
170 
171 void View::disappear(const std::string& eid) {
172  auto* ent = getEntity(eid);
173  if (ent) {
174  deleteEntity(eid);
175  } else {
176  if (isPending(eid)) {
177  //debug() << "got disappearance for pending " << eid;
178  m_pending[eid].sightAction = SightAction::DISCARD;
179  } else {
180  warning() << "got disappear for unknown entity " << eid;
181  }
182  }
183 }
184 
185 void View::sight(const RootEntity& gent) {
186  bool visible = true;
187  std::string eid = gent->getId();
188  auto pending = m_pending.find(eid);
189 
190 // examine the pending map, to see what we should do with this entity
191  if (pending != m_pending.end()) {
192  switch (pending->second.sightAction) {
193  case SightAction::APPEAR:
194  visible = true;
195  break;
196 
197  case SightAction::DISCARD:
198  m_pending.erase(pending);
199  issueQueuedLook();
200  return;
201 
202  case SightAction::HIDE:
203  visible = false;
204  break;
205 
206  case SightAction::QUEUED:
207  error() << "got sight of queued entity " << eid << " somehow";
208  eraseFromLookQueue(eid);
209  break;
210 
211  default:
212  throw InvalidOperation("got bad pending action for entity");
213  }
214 
215  m_pending.erase(pending);
216  }
217 
218 // if we got this far, go ahead and build / update it
219  auto* ent = getEntity(eid);
220  if (ent) {
221  // existing entity, update in place
222  ent->firstSight(gent);
223  } else {
224  ent = initialSight(gent);
225  EntitySeen.emit(ent);
226  }
227 
228  ent->setVisible(visible);
229  issueQueuedLook();
230 }
231 
232 ViewEntity* View::initialSight(const RootEntity& gent) {
233  assert(m_contents.count(gent->getId()) == 0);
234 
235  auto entity = createEntity(gent);
236  auto router = std::make_unique<EntityRouter>(*entity, *this);
237 
238  auto entityPtr = entity.get();
239  //Don't store connection as life time of entity is bound to the view.
240  entity->Moving.connect([this, entityPtr](bool startedMoving) {
241  if (startedMoving) {
242  addToPrediction(entityPtr);
243  } else {
244  removeFromPrediction(entityPtr);
245  }
246  });
247 
248  auto I = m_contents.emplace(gent->getId(), EntityEntry{std::move(entity), std::move(router)});
249  auto& insertedEntry = I.first->second;
250  auto insertedEntity = insertedEntry.entity.get();
251  insertedEntity->init(gent, false);
252 
253  InitialSightEntity.emit(insertedEntity);
254 
255  auto it = m_notifySights.find(gent->getId());
256  if (it != m_notifySights.end()) {
257  it->second.emit(insertedEntity);
258  m_notifySights.erase(it);
259  }
260 
261  return insertedEntity;
262 }
263 
264 void View::deleteEntity(const std::string& eid) {
265  auto I = m_contents.find(eid);
266  if (I != m_contents.end()) {
267 
268  auto entity = I->second.entity.get();
269  if (entity->m_moving) {
270  removeFromPrediction(entity);
271  }
272  //If the entity being deleted is an ancestor to the observer, we should detach the nearest closest entity below it (which often is the observer)
273  //and then delete all entities above it (if any, which there often aren't any of).
274  if (entity->isAncestorTo(*m_owner.getEntity())) {
275  Entity* nearestAncestor = m_owner.getEntity();
276  while (nearestAncestor->getLocation() != entity) {
277  nearestAncestor = nearestAncestor->getLocation();
278  }
279  //Remove the nearest ancestor from its parent first
280  nearestAncestor->setLocation(nullptr, true);
281  //The new top level should be the previous nearest ancestor.
282  setTopLevelEntity(nearestAncestor);
283  //If the current entity also has parents, delete them too.
284  if (entity->getLocation()) {
285  auto entityLocation = entity->getLocation();
286  //First remove current location from parent, so we don't delete twice
287  entity->setLocation(nullptr, true);
288  //Delete the whole tree of entities, starting at top
289  deleteEntity(entityLocation->getTopEntity()->getId());
290  }
291 
292  }
293 
294  //Emit signals about the entity being deleted.
295  EntityDeleted.emit(entity);
296  entity->BeingDeleted.emit();
297  //We need to delete all children too.
298  auto children = I->second.entity->getContent();
299  m_contents.erase(I);
300  for (auto& child : children) {
301  deleteEntity(child->getId());
302  }
303 
304  } else {
305  //We might get a delete for an entity which we are awaiting info about; this is normal.
306  if (isPending(eid)) {
307  m_pending[eid].sightAction = SightAction::DISCARD;
308  } else {
309  warning() << "got delete for unknown entity " << eid;
310  }
311  }
312 }
313 
314 std::unique_ptr<ViewEntity> View::createEntity(const RootEntity& gent) {
315  TypeInfo* type = getConnection().getTypeService().getTypeForAtlas(gent);
316  assert(type->isBound());
317 
318  auto F = m_factories.begin();
319  for (; F != m_factories.end(); ++F) {
320  if ((*F)->accept(gent, type)) {
321  return (*F)->instantiate(gent, type, *this);
322  }
323  }
324 
325  throw std::runtime_error("Could not find entity factory suitable for creating new entity.");
326 }
327 
328 void View::unseen(const std::string& eid) {
329  //This op is received when we tried to interact with something we can't observe anymore (either because it's deleted
330  // or because it's out of sight).
331  deleteEntity(eid);
332  //Remove any pending status.
333  m_pending.erase(eid);
334 }
335 
336 bool View::isPending(const std::string& eid) const {
337  return m_pending.find(eid) != m_pending.end();
338 }
339 
340 Connection& View::getConnection() const {
341  return m_owner.getConnection();
342 }
343 
344 void View::getEntityFromServer(const std::string& eid) {
345  if (isPending(eid)) {
346  return;
347  }
348 
349  // don't apply pending queue cap for anonymous LOOKs
350  if (!eid.empty() && (m_pending.size() >= m_maxPendingCount)) {
351  m_lookQueue.push_back(eid);
352  m_pending[eid].sightAction = SightAction::QUEUED;
353  return;
354  }
355 
356  sendLookAt(eid);
357 }
358 
359 size_t View::pruneAbandonedPendingEntities() {
360  size_t pruned = 0;
361  auto now = std::chrono::steady_clock::now();
362  for (auto I = m_pending.begin(); I != m_pending.end();) {
363  if (I->second.sightAction != SightAction::QUEUED && (now - I->second.registrationTime) > std::chrono::seconds(20)) {
364  warning() << "Didn't receive any response for entity " << I->first << " within 20 seconds, will remove it from pending list.";
365  I = m_pending.erase(I);
366  pruned++;
367  } else {
368  ++I;
369  }
370  }
371  return pruned;
372 }
373 
374 
375 void View::sendLookAt(const std::string& eid) {
376  Look look;
377  if (!eid.empty()) {
378  auto pending = m_pending.find(eid);
379  if (pending != m_pending.end()) {
380  switch (pending->second.sightAction) {
381  case SightAction::QUEUED:
382  // flip over to default (APPEAR) as normal
383  pending->second.sightAction = SightAction::APPEAR;
384  break;
385 
386  case SightAction::DISCARD:
387  case SightAction::HIDE:
388  if (m_notifySights.count(eid) == 0) {
389  // no-one cares, don't bother to look
390  m_pending.erase(pending);
391  issueQueuedLook();
392  return;
393  } // else someone <em>does</em> care, so let's do the look, but
394  // keep SightAction unchanged so it discards / is hidden as
395  // expected.
396  break;
397 
398  case SightAction::APPEAR:
399  // this can happen if a queued entity disappears and then
400  // re-appears, all while in the look queue. we can safely fall
401  // through.
402  break;
403 
404  default:
405  // broken state handling logic
406  assert(false);
407  break;
408  }
409  } else {
410  // no previous entry, default to APPEAR
411  m_pending.emplace(eid, PendingStatus{SightAction::APPEAR, std::chrono::steady_clock::now()});
412  }
413 
414  // pending map is in the right state, build up the args now
415  Root what;
416  what->setId(eid);
417  look->setArgs1(what);
418  }
419 
420  look->setFrom(m_owner.getId());
421  getConnection().send(look);
422 }
423 
424 void View::setTopLevelEntity(Entity* newTopLevel) {
425  if (newTopLevel == m_topLevel) {
426  return; // no change!
427  }
428 
429  m_simulationSpeedConnection.disconnect();
430 
431  if (newTopLevel) {
432  assert(newTopLevel->getLocation() == nullptr);
433  m_simulationSpeedConnection = newTopLevel->observe("simulation_speed", sigc::mem_fun(this, &View::parseSimulationSpeed), true);
434  }
435  m_topLevel = newTopLevel;
436  TopLevelEntityChanged.emit(); // fire the signal
437 }
438 
439 
440 void View::parseSimulationSpeed(const Atlas::Message::Element& element) {
441  if (element.isFloat()) {
442  m_simulationSpeed = element.Float();
443  }
444 }
445 
446 void View::issueQueuedLook() {
447  if (m_lookQueue.empty()) {
448  return;
449  }
450  std::string eid = std::move(m_lookQueue.front());
451  m_lookQueue.pop_front();
452  sendLookAt(eid);
453 }
454 
455 void View::dumpLookQueue() {
456  debug() << "look queue:";
457  for (const auto& lookOp : m_lookQueue) {
458  debug() << "\t" << lookOp;
459  }
460 }
461 
462 void View::eraseFromLookQueue(const std::string& eid) {
463  std::deque<std::string>::iterator it;
464  for (it = m_lookQueue.begin(); it != m_lookQueue.end(); ++it) {
465  if (*it == eid) {
466  m_lookQueue.erase(it);
467  return;
468  }
469  }
470 
471  error() << "entity " << eid << " not present in the look queue";
472 }
473 
474 } // of namespace Eris
void firstSight(const Atlas::Objects::Entity::RootEntity &gent)
Definition: Entity.cpp:254
sigc::connection notifyWhenEntitySeen(const std::string &eid, const EntitySightSlot &slot)
Definition: View.cpp:58
bool isPending(const std::string &eid) const
test if the specified entity ID is pending initial sight on the View
Definition: View.cpp:336
void update()
Definition: View.cpp:89
void setVisible(bool vis)
Definition: Entity.cpp:762
bool isMoving() const
Test if this entity has a non-zero velocity vector.
Definition: Entity.cpp:224
The representation of an Atlas type (i.e a class or operation definition). This class supports effice...
Definition: TypeInfo.h:32
virtual Entity * getEntity(const std::string &id)=0
Gets an entity with the supplied id from the system.
void setLocation(Entity *newLocation, bool removeFromOldLocation=true)
Definition: Entity.cpp:644
void taskRateChanged(Task *)
Definition: View.cpp:134
Definition: Account.cpp:33
void sendLookAt(const std::string &eid)
Definition: View.cpp:375
Handles polling of the IO system as well as making sure that registered handlers are run on the main ...
Definition: EventService.h:42
bool m_recentlyCreated
flag set if this entity was the subject of a sight(create)
Definition: Entity.h:622
bool isBound() const
Check the bound flag for this node; if false then recursivley check parents until an authorative is f...
Definition: TypeInfo.h:212
TypeInfo * getTypeForAtlas(const Atlas::Objects::Root &obj)
Definition: TypeService.cpp:77
double getStamp() const
Access the current time-stamp of the entity.
Definition: Entity.h:645
sigc::connection observe(const std::string &propertyName, const PropertyChangedSlot &aslot, bool evaluateNow)
Setup an observer so that the specified slot is fired when the named property&#39;s value changes...
Definition: Entity.cpp:196
An entity which is bound to an Eris::View. This subclass of Eris::Entity is intimately bound to a Vie...
Definition: ViewEntity.h:21
TypeService & getTypeService()
Gets the TypeService attached to the view.
Definition: View.cpp:69
bool isVisible() const
Definition: Entity.cpp:774
Entity is a concrete (instantiable) class representing one game entity.
Definition: Entity.h:55
Entity * getLocation() const
The containing entity, or null if this is a top-level visible entity.
Definition: Entity.h:656
ViewEntity * getEntity(const std::string &eid) const
Definition: View.cpp:45
void registerFactory(std::unique_ptr< Factory > factory)
Definition: View.cpp:54
EventService & getEventService()
Gets the EventService used by the view.
Definition: View.cpp:77