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
20using namespace Atlas::Objects::Operation;
21using Atlas::Objects::Root;
22using Atlas::Objects::Entity::RootEntity;
23using Atlas::Objects::smart_dynamic_cast;
24
25namespace Eris {
26
27View::View(Avatar& av) :
28 m_owner(av),
29 m_topLevel(nullptr),
30 m_simulationSpeed(1.0),
31 m_maxPendingCount(10) {
32}
33
34View::~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
45ViewEntity* 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
54void View::registerFactory(std::unique_ptr<Factory> f) {
55 m_factories.insert(std::move(f));
56}
57
58sigc::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
69TypeService& View::getTypeService() {
70 return m_owner.getConnection().getTypeService();
71}
72
73TypeService& View::getTypeService() const {
74 return m_owner.getConnection().getTypeService();
75}
76
77EventService& View::getEventService() {
78 return m_owner.getConnection().getEventService();
79}
80
81EventService& View::getEventService() const {
82 return m_owner.getConnection().getEventService();
83}
84
85double View::getSimulationSpeed() const {
86 return m_simulationSpeed;
87}
88
89void 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
123void View::addToPrediction(ViewEntity* ent) {
124 assert(ent->isMoving());
125 assert(m_moving.count(ent) == 0);
126 m_moving.insert(ent);
127}
128
129void View::removeFromPrediction(ViewEntity* ent) {
130 assert(m_moving.count(ent) == 1);
131 m_moving.erase(ent);
132}
133
134void View::taskRateChanged(Task* t) {
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
144void 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
171void 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
185void 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
232ViewEntity* 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
264void 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
314std::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
328void 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
336bool View::isPending(const std::string& eid) const {
337 return m_pending.find(eid) != m_pending.end();
338}
339
340Connection& View::getConnection() const {
341 return m_owner.getConnection();
342}
343
344void 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
359size_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
375void 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
424void 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
440void View::parseSimulationSpeed(const Atlas::Message::Element& element) {
441 if (element.isFloat()) {
442 m_simulationSpeed = element.Float();
443 }
444}
445
446void 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
455void View::dumpLookQueue() {
456 debug() << "look queue:";
457 for (const auto& lookOp : m_lookQueue) {
458 debug() << "\t" << lookOp;
459 }
460}
461
462void 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
Entity is a concrete (instantiable) class representing one game entity.
Definition: Entity.h:56
bool isMoving() const
Test if this entity has a non-zero velocity vector.
Definition: Entity.cpp:224
Entity * getLocation() const
The containing entity, or null if this is a top-level visible entity.
Definition: Entity.h:656
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's value changes.
Definition: Entity.cpp:196
Handles polling of the IO system as well as making sure that registered handlers are run on the main ...
Definition: EventService.h:43
An entity which is bound to an Eris::View. This subclass of Eris::Entity is intimately bound to a Vie...
Definition: ViewEntity.h:21
Definition: Account.cpp:33