cyphesis/tools/EntityImporterBase.cpp
2018-10-09 10:24:55 +02:00

1046 lines
37 KiB
C++

//
// Copyright (C) 2009 Alistair Riddoch
// Copyright (C) 2013 Erik Ogenvik
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include "EntityImporterBase.h"
#include <Atlas/Objects/Anonymous.h>
#include <Atlas/Objects/Operation.h>
#include <boost/optional.hpp>
#include <fstream>
#include <sstream>
#include <iostream>
using Atlas::Objects::Root;
using Atlas::Objects::smart_dynamic_cast;
using Atlas::Objects::Entity::Anonymous;
using Atlas::Objects::Entity::RootEntity;
using Atlas::Objects::Operation::Create;
using Atlas::Objects::Operation::Get;
using Atlas::Objects::Operation::Look;
using Atlas::Objects::Operation::Set;
using Atlas::Message::Element;
//The following are adapters to make the Ember logging syntax work with the Cyphesis log.
#include "common/log.h"
#include "common/debug.h"
static const bool debug_flag = true;
#define S_LOG_VERBOSE(message) \
{ debug(std::stringstream ss;\
ss << message;\
log(NOTICE, ss.str());) }
#define S_LOG_INFO(message) \
{ std::stringstream ss;\
ss << message;\
log(INFO, ss.str()); }
#define S_LOG_WARNING(message) \
{ std::stringstream ss;\
ss << message;\
log(WARNING, ss.str()); }
#define S_LOG_FAILURE(message) \
{ std::stringstream ss;\
ss << message;\
log(ERROR, ss.str()); }
#define S_LOG_CRITICAL(message) \
{ std::stringstream ss;\
ss << message;\
log(CRITICAL, ss.str()); }
StackEntry::StackEntry(const Atlas::Objects::Entity::RootEntity & o, const std::list<std::string>::const_iterator & c) :
obj(o), currentChildIterator(c)
{
}
StackEntry::StackEntry(const Atlas::Objects::Entity::RootEntity & o) :
obj(o)
{
currentChildIterator = obj->getContains().end();
}
bool EntityImporterBase::getEntity(const std::string & id, OpVector & res)
{
std::map<std::string, Root>::const_iterator I = mPersistedEntities.find(id);
if (I == mPersistedEntities.end()) {
S_LOG_VERBOSE("Could not find entity with id " << id << "; this one was probably transient.");
//This will often happen if the child entity was transient, and therefore wasn't exported (but is still references from the parent entity).
return false;
}
const RootEntity& obj = smart_dynamic_cast<RootEntity>(I->second);
if (!obj.isValid()) {
S_LOG_FAILURE("Corrupt dump - non entity found " << id << ".");
return false;
}
m_state = ENTITY_WALKING;
mTreeStack.emplace_back(obj);
Anonymous get_arg;
get_arg->setId(id);
get_arg->setObjtype("obj");
Get get;
get->setArgs1(get_arg);
get->setFrom(mAccountId);
get->setSerialno(newSerialNumber());
res.push_back(get);
S_LOG_VERBOSE("EntityImporterBase: Getting entity with id " << id);
return true;
}
void EntityImporterBase::extractChildren(const Root& op, std::list<std::string>& children)
{
Element childElem;
if (op->copyAttr("children", childElem) == 0) {
if (childElem.isList()) {
for (auto child : childElem.asList()) {
if (child.isString()) {
children.push_back(child.String());
}
}
}
}
}
bool EntityImporterBase::getRule(const std::string & id, OpVector & res)
{
auto I = mPersistedRules.find(id);
if (I == mPersistedRules.end()) {
S_LOG_WARNING("Could not find rule with id " << id << ".");
return false;
}
auto definition = I->second;
m_state = RULE_WALKING;
std::list<std::string> children;
extractChildren(definition, children);
RuleStackEntry entry = { id, definition, children };
mRuleStack.push_back(entry);
Get get;
Anonymous arg;
arg->setId(id);
get->setArgs1(arg);
// get->setObjtype("op");
get->setSerialno(newSerialNumber());
res.push_back(get);
Anonymous get_arg;
get_arg->setId(id);
get_arg->setObjtype("obj");
return true;
}
void EntityImporterBase::walkRules(OpVector & res)
{
if (mRuleStack.empty()) {
startEntityWalking();
} else {
auto & current = mRuleStack.back();
auto definition = current.definition;
//Check if there are any children. If not, we should pop the stack and
if (current.children.empty()) {
// Pop: Go back to WALKING parent
assert(!mRuleStack.empty());
mRuleStack.pop_back();
while (!mRuleStack.empty()) {
auto & se = mRuleStack.back();
//Try to get the next child rule (unless we've reached the end of the list of children).
for (; ++se.currentChildIterator, se.currentChildIterator != se.children.end();) {
if (getRule(*se.currentChildIterator, res)) {
//We've sent a request for a rule; we should break out and await a response from the server.
return;
}
}
//If we've reached the end of the contained rules we should pop the current stack entry.
mRuleStack.pop_back();
}
} else {
for (auto I = current.children.begin(); I != current.children.end(); ++I) {
current.currentChildIterator = I;
bool foundRule = getRule(*current.currentChildIterator, res);
if (foundRule) {
return;
}
}
}
if (mRuleStack.empty()) {
startEntityWalking();
}
}
}
void EntityImporterBase::walkEntities(OpVector & res)
{
if (mTreeStack.empty()) {
sendResolvedEntityReferences();
} else {
StackEntry & current = mTreeStack.back();
//Check if there are any children. If not, we should pop the stack and
if (current.obj->getContains().empty()) {
// Pop: Go back to WALKING parent
assert(!mTreeStack.empty());
mTreeStack.pop_back();
while (!mTreeStack.empty()) {
StackEntry & se = mTreeStack.back();
//Try to get the next child entity (unless we've reached the end of the list of children).
//Since some entities are references but not persisted we need to loop until we find one that we know of.
for (; ++se.currentChildIterator, se.currentChildIterator != se.obj->getContains().end();) {
if (getEntity(*se.currentChildIterator, res)) {
//We've sent a request for an entity; we should break out and await a response from the server.
return;
}
}
//If we've reached the end of the contained entities we should pop the current stack entry.
mTreeStack.pop_back();
}
} else {
//Iterate until we find an entity that's persisted.
//Note that there might be a reference to an entity in CONTAINS which can't be found
//in the list of entities; this happens with transient entities.
for (auto I = current.obj->getContains().begin(); I != current.obj->getContains().end(); ++I) {
current.currentChildIterator = I;
bool foundEntity = getEntity(*current.currentChildIterator, res);
if (foundEntity) {
return;
}
}
}
if (mTreeStack.empty()) {
sendResolvedEntityReferences();
}
}
}
void EntityImporterBase::sendMinds()
{
if (!mResolvedMindMapping.empty()) {
S_LOG_INFO("Sending minds.");
for (auto mind : mResolvedMindMapping) {
Atlas::Message::MapType message;
mind.second->addToMessage(message);
Atlas::Message::Element thoughtsElem = message["thoughts"];
Atlas::Message::ListType thoughtArgs;
if (thoughtsElem.isList()) {
Atlas::Message::ListType thoughtList = thoughtsElem.List();
for (auto& thought : thoughtList) {
//If the thought is a list of things the entity owns, we should adjust it with the new entity ids.
if (thought.isMap()) {
auto& thoughtMap = thought.Map();
if (thoughtMap.count("things") > 0) {
auto& thingsElement = thoughtMap.find("things")->second;
if (thingsElement.isMap()) {
for (auto& thingI : thingsElement.asMap()) {
if (thingI.second.isList()) {
Atlas::Message::ListType newList;
for (auto& thingId : thingI.second.asList()) {
if (thingId.isString()) {
const auto& entityIdLookupI = mEntityIdMap.find(thingId.asString());
//Check if the owned entity has been created with a new id. If so, replace the data.
if (entityIdLookupI != mEntityIdMap.end()) {
newList.emplace_back(entityIdLookupI->second);
} else {
newList.push_back(thingId);
}
} else {
newList.push_back(thingId);
}
}
thingI.second = newList;
}
}
}
}
if (thoughtMap.count("pending_things") > 0) {
//things that the entity owns, but haven't yet discovered are expressed as a list of entity ids
auto& pendingThingsElement = thoughtMap.find("pending_things")->second;
if (pendingThingsElement.isList()) {
Atlas::Message::ListType newList;
for (auto& thingId : pendingThingsElement.asList()) {
if (thingId.isString()) {
const auto& entityIdLookupI = mEntityIdMap.find(thingId.asString());
//Check if the owned entity has been created with a new id. If so, replace the data.
if (entityIdLookupI != mEntityIdMap.end()) {
newList.emplace_back(entityIdLookupI->second);
} else {
newList.push_back(thingId);
}
} else {
newList.push_back(thingId);
}
}
pendingThingsElement = newList;
}
}
if (thoughtMap.count("object") > 0) {
auto& objectElement = thoughtMap.find("object")->second;
if (objectElement.isString()) {
std::string& objectString = objectElement.String();
//Other entities are referred to using the syntax "'$eid:...'".
//For example, the entity with id 2 would be "'$eid:2'".
auto pos = objectString.find("$eid:");
if (pos != std::string::npos) {
auto quotePos = objectString.find('\'', pos);
if (quotePos != std::string::npos) {
auto id = objectString.substr(pos + 5, quotePos - pos - 5);
auto I = mEntityIdMap.find(id);
if (I != mEntityIdMap.end()) {
objectString.replace(pos + 5, quotePos - 7, I->second);
}
}
}
}
}
}
thoughtArgs.push_back(thought);
}
}
Atlas::Objects::Operation::RootOperation thinkOp;
thinkOp->setParent("think");
thinkOp->setTo(mind.first);
//By setting it TO an entity and FROM our avatar we'll make the server deliver it as
//if it came from the entity itself (the server rewrites the FROM to be of the entity).
thinkOp->setFrom(mAvatarId);
thinkOp->setSerialno(newSerialNumber());
Atlas::Objects::Operation::Set setOp;
setOp->setArgsAsList(thoughtArgs);
thinkOp->setArgs1(setOp);
mStats.mindsProcessedCount++;
S_LOG_VERBOSE("Restoring mind of " << mind.first);
mThoughtOpsInTransit++;
sigc::slot<void, const Operation&> slot = sigc::mem_fun(*this, &EntityImporterBase::operationThinkResult);
sendAndAwaitResponse(thinkOp, slot);
EventProgress.emit();
}
mResolvedMindMapping.clear();
} else {
complete();
}
}
void EntityImporterBase::sendResolvedEntityReferences()
{
if (!mEntitiesWithReferenceAttributes.empty()) {
for (auto entryI : mEntitiesWithReferenceAttributes) {
const auto& persistedEntityId = entryI.first;
const auto& referenceEntries = entryI.second;
auto createdEntityI = mEntityIdMap.find(persistedEntityId);
if (createdEntityI == mEntityIdMap.end()) {
S_LOG_WARNING("Could not find final server side entity id for persisted entity " << persistedEntityId << " when doing entity ref resolving.");
continue;
}
const auto& createdEntityId = createdEntityI->second;
//This should not fail at this phase, so we're not doing any checks.
auto& persistedEntity = mPersistedEntities.find(persistedEntityId)->second;
RootEntity entity;
for (const auto& referenceEntry : referenceEntries) {
Element element = persistedEntity->getAttr(referenceEntry.propertyName);
resolveEntityReferences(element);
entity->setAttr(referenceEntry.propertyName, element);
}
Set set;
set->setFrom(mAvatarId);
set->setSerialno(newSerialNumber());
set->setTo(createdEntityId);
set->setArgs1(entity);
mSetOpsInTransit++;
sigc::slot<void, const Operation&> slot = sigc::mem_fun(*this, &EntityImporterBase::operationSetResult);
sendAndAwaitResponse(set, slot);
}
} else {
sendMinds();
}
}
void EntityImporterBase::resolveEntityReferences(Atlas::Message::Element& element)
{
if (element.isMap()) {
auto entityRefI = element.asMap().find("$eid");
if (entityRefI != element.asMap().end() && entityRefI->second.isString()) {
auto I = mEntityIdMap.find(entityRefI->second.asString());
if (I != mEntityIdMap.end()) {
entityRefI->second = I->second;
}
}
//If it's a map we need to process all child elements too
for (auto& I : element.asMap()) {
resolveEntityReferences(I.second);
}
} else if (element.isList()) {
//If it's a list we need to process all child elements too
for (auto& I : element.asList()) {
resolveEntityReferences(I);
}
}
}
void EntityImporterBase::complete()
{
S_LOG_INFO("Restore done.");
S_LOG_INFO("Restored " << mStats.entitiesProcessedCount<< ", created: " << mStats.entitiesCreateCount << ", updated: " << mStats.entitiesUpdateCount << ", create errors: " << mStats.entitiesCreateErrorCount << " .");
EventCompleted.emit();
}
void EntityImporterBase::createEntity(const RootEntity & obj, OpVector & res)
{
++mStats.entitiesProcessedCount;
++mStats.entitiesCreateCount;
EventProgress.emit();
m_state = ENTITY_CREATING;
assert(mTreeStack.size() > 1);
auto I = mTreeStack.rbegin();
++I;
assert(I != mTreeStack.rend());
const std::string & loc = I->restored_id;
RootEntity create_arg = obj.copy();
create_arg->removeAttrFlag(Atlas::Objects::Entity::CONTAINS_FLAG);
create_arg->removeAttrFlag(Atlas::Objects::Entity::VELOCITY_FLAG);
create_arg->removeAttrFlag(Atlas::Objects::ID_FLAG);
create_arg->setLoc(loc);
//Remove any attribute which references another entity from the Create op.
//This is because the attribute will at this time with certainty refer to the wrong or a non-existing entity.
//The attribute will later on be set through a Set op in sendResolvedEntityReferences().
auto referenceMapEntryI = mEntitiesWithReferenceAttributes.find(obj->getId());
if (referenceMapEntryI != mEntitiesWithReferenceAttributes.end()) {
std::set<std::string> resolvedAttributes;
for (const auto& referenceEntry : referenceMapEntryI->second) {
size_t resolvedEntitiesCount = 0;
//Check if all the referenced entities perhaps already have been created.
for (const auto& entityId : referenceEntry.referencedEntities) {
auto resolvedI = mEntityIdMap.find(entityId);
if (resolvedI != mEntityIdMap.end()) {
resolvedEntitiesCount++;
}
}
//If all entities were resolved, we should resolve the property now.
if (resolvedEntitiesCount == referenceEntry.referencedEntities.size()) {
Element element = create_arg->getAttr(referenceEntry.propertyName);
resolveEntityReferences(element);
create_arg->setAttr(referenceEntry.propertyName, element);
resolvedAttributes.insert(referenceEntry.propertyName);
} else {
create_arg->removeAttr(referenceEntry.propertyName);
}
}
//Remove those attributes that were resolved
if (resolvedAttributes.size() == referenceMapEntryI->second.size()) {
//All attributes were resolved, remove the entry completely.
mEntitiesWithReferenceAttributes.erase(referenceMapEntryI);
} else {
//Only remove those entries that were destroyed.
std::vector<ReferencedEntry> copy;
for (auto& referenceEntry : referenceMapEntryI->second) {
if (resolvedAttributes.find(referenceEntry.propertyName) == resolvedAttributes.end()) {
copy.push_back(std::move(referenceEntry));
}
}
referenceMapEntryI->second = std::move(copy);
}
}
Create create;
create->setArgs1(create_arg);
create->setFrom(mAvatarId);
create->setSerialno(newSerialNumber());
mCreateEntityMapping.insert(std::make_pair(create->getSerialno(), obj->getId()));
res.push_back(create);
}
void EntityImporterBase::createRule(const Atlas::Objects::Root & obj, OpVector & res)
{
m_state = RULE_CREATING;
Atlas::Objects::Operation::Create createOp;
createOp->setFrom(mAccountId);
createOp->setArgs1(obj);
createOp->setSerialno(newSerialNumber());
S_LOG_INFO("Creating new rule '" << obj->getId() << "' on server.");
res.push_back(createOp);
}
void EntityImporterBase::updateRule(const Root& existingDefinition, const Root& newDefinition, OpVector & res)
{
m_state = RULE_UPDATING;
Root updatedDefinition = newDefinition.copy();
//If the existing definition had any children that aren't part of the import, we should preserve those
std::list<std::string> existingChildren, newChildren;
extractChildren(existingDefinition, existingChildren);
extractChildren(newDefinition, newChildren);
for (auto& child : newChildren) {
existingChildren.remove(child);
}
for (auto& child : existingChildren) {
if (mPersistedRules.find(child) == mPersistedRules.end()) {
newChildren.push_back(child);
}
}
if (!newChildren.empty() && !existingChildren.empty()) {
Atlas::Message::ListType childrenElement;
for (auto& child : newChildren) {
childrenElement.emplace_back(child);
}
updatedDefinition->setAttr("children", childrenElement);
}
if (updatedDefinition->asMessage() != existingDefinition->asMessage()) {
S_LOG_INFO("Updating server rule '"<< updatedDefinition->getId() << "'.");
Atlas::Objects::Operation::Set setOp;
setOp->setFrom(mAccountId);
setOp->setArgs1(updatedDefinition);
setOp->setSerialno(newSerialNumber());
res.push_back(setOp);
} else {
mStats.rulesProcessedCount++;
EventProgress.emit();
S_LOG_VERBOSE("Not updating server rule '"<< updatedDefinition->getId() << "' as nothing is changed.");
walkRules(res);
}
}
void EntityImporterBase::errorArrived(const Operation & op, OpVector & res)
{
std::string errorMessage;
if (!op->getArgs().empty()) {
auto arg = op->getArgs().front();
if (arg->hasAttr("message")) {
const Atlas::Message::Element messageElem = arg->getAttr("message");
if (messageElem.isString()) {
errorMessage = messageElem.asString();
}
}
}
switch (m_state) {
case RULE_WALKING:
{
//An error here just means that the rule we asked for didn't exist on the server, and we need
//to create it. This is an expected result.
auto& current = mRuleStack.back();
auto definition = current.definition;
assert(definition.isValid());
createRule(definition, res);
}
break;
case RULE_CREATING:
{
mStats.rulesProcessedCount++;
mStats.rulesCreateErrorCount++;
//An error here means that something went wrong when trying to create a rule. This is wrong.
//It probably means that there's something wrong with the data we're sending.
auto& current = mRuleStack.back();
std::string ruleId = current.definition->getId();
S_LOG_FAILURE("Could not create rule with id '" << ruleId << "', continuing with next. Server message: " << errorMessage);
EventProgress.emit();
walkRules(res);
}
break;
case RULE_UPDATING:
{
mStats.rulesProcessedCount++;
//An error here means that something went wrong when trying to update a rule. This is wrong.
//It probably means that there's something wrong with the data we're sending.
auto& current = mRuleStack.back();
std::string ruleId = current.definition->getId();
S_LOG_FAILURE("Could not update rule with id '" << ruleId << "', continuing with next. Server message: " << errorMessage);
mStats.rulesCreateErrorCount++;
EventProgress.emit();
walkRules(res);
}
break;
case ENTITY_WALKING:
{
//An error here just means that the entity we asked for didn't exist on the server, and we need
//to create it. This is an expected result.
assert(!mTreeStack.empty());
StackEntry & current = mTreeStack.back();
const RootEntity& obj = current.obj;
assert(obj.isValid());
createEntity(obj, res);
}
break;
case ENTITY_CREATING:
{
//An error here means that something went wrong when trying to create an entity. This is wrong.
//It probably means that there's something wrong with the data we're sending. Either the
//persisted data is corrupt, or there have been changes on the server (for example entity types
//renamed or removed).
std::string entityType = "unknown";
auto I = mCreateEntityMapping.find(op->getRefno());
if (I != mCreateEntityMapping.end()) {
auto J = mPersistedEntities.find(I->second);
if (J != mPersistedEntities.end()) {
auto& entity = J->second;
entityType = entity->getParent();
}
}
S_LOG_FAILURE("Could not create entity of type '" << entityType << "', continuing with next. Server message: " << errorMessage);
mStats.entitiesCreateErrorCount++;
EventProgress.emit();
walkEntities(res);
}
break;
default:
S_LOG_FAILURE("Unexpected state in state machine: " << m_state << ". Server message: " << errorMessage);
break;
};
}
void EntityImporterBase::infoArrived(const Operation & op, OpVector & res)
{
if (op->isDefaultRefno()) {
return;
}
if (op->isDefaultArgs() || op->getArgs().empty()) {
S_LOG_FAILURE("Info with no arg.");
return;
}
const Root & arg = op->getArgs().front();
if (m_state == RULE_WALKING) {
auto& current = mRuleStack.back();
if (arg->getId() != current.definition->getId()) {
S_LOG_WARNING("Got info on rule " << arg->getId() << " when expecting " << current.definition->getId() << ".");
return;
}
updateRule(arg, current.definition, res);
} else if (m_state == RULE_CREATING) {
mStats.rulesProcessedCount++;
mStats.rulesCreateCount++;
EventProgress.emit();
walkRules(res);
} else if (m_state == RULE_UPDATING) {
mStats.rulesProcessedCount++;
mStats.rulesUpdateCount++;
EventProgress.emit();
walkRules(res);
} else if (m_state == ENTITY_CREATING) {
if (!op.isValid()) {
return;
}
mNewIds.insert(arg->getId());
StackEntry & current = mTreeStack.back();
current.restored_id = arg->getId();
S_LOG_VERBOSE("Created: " << arg->getParent() << "(" << arg->getId() << ")");
auto I = mCreateEntityMapping.find(op->getRefno());
if (I != mCreateEntityMapping.end()) {
//Check if there's a mind that we should send further on
auto mindI = mPersistedMinds.find(I->second);
if (mindI != mPersistedMinds.end()) {
mResolvedMindMapping.emplace_back(arg->getId(), mindI->second);
}
mEntityIdMap.insert(std::make_pair(I->second, arg->getId()));
mCreateEntityMapping.erase(op->getRefno());
} else {
S_LOG_WARNING("Got info about create for an entity which we didn't seem to have sent.");
}
walkEntities(res);
} else if (m_state == ENTITY_WALKING) {
const RootEntity& ent = smart_dynamic_cast<RootEntity>(arg);
if (!ent.isValid()) {
S_LOG_FAILURE("Info response is not entity.");
return;
}
if (arg->isDefaultId()) {
S_LOG_FAILURE("Corrupted info response: no id.");
}
const std::string & id = arg->getId();
StackEntry & current = mTreeStack.back();
const RootEntity& obj = current.obj;
assert(id == obj->getId());
if (mNewIds.find(id) != mNewIds.end() || (mTreeStack.size() != 1 && ent->isDefaultLoc()) || ent->getParent() != obj->getParent()) {
createEntity(obj, res);
} else {
Root update = obj.copy();
current.restored_id = id;
S_LOG_VERBOSE("Updating: " << obj->getId() << " ," << obj->getParent());
update->removeAttrFlag(Atlas::Objects::Entity::CONTAINS_FLAG);
update->removeAttrFlag(Atlas::Objects::STAMP_FLAG);
Set set;
set->setArgs1(update);
set->setFrom(mAvatarId);
set->setTo(id);
set->setSerialno(newSerialNumber());
res.push_back(set);
//Check if there's a mind, and if so put it in our map of resolved entity-to-mind mappings (since we know the entity id)
auto mindI = mPersistedMinds.find(obj->getId());
if (mindI != mPersistedMinds.end()) {
mResolvedMindMapping.emplace_back(obj->getId(), mindI->second);
}
++mStats.entitiesProcessedCount;
++mStats.entitiesUpdateCount;
EventProgress.emit();
m_state = ENTITY_UPDATING;
}
}
}
void EntityImporterBase::sightArrived(const Operation & op, OpVector & res)
{
if (op->isDefaultArgs() || op->getArgs().empty()) {
S_LOG_FAILURE("No arg");
return;
}
const Root & arg = op->getArgs().front();
switch (m_state) {
case ENTITY_WALKSTART:
if (op->isDefaultRefno()) {
break;
}
if (arg->isDefaultId()) {
S_LOG_WARNING("Corrupted top level entity: no id");
cancel();
return;
} else {
getEntity(arg->getId(), res);
}
// Expecting sight of world root
break;
case ENTITY_UPDATING:
{
const Operation& sub_op = smart_dynamic_cast<Operation>(arg);
if (!sub_op.isValid()) {
break;
}
if (sub_op->getClassNo() != Atlas::Objects::Operation::SET_NO || sub_op->getArgs().empty() || sub_op->isDefaultSerialno()) {
S_LOG_FAILURE("This is not our entity update response.");
break;
}
walkEntities(res);
}
break;
case ENTITY_CREATING:
case ENTITY_WALKING:
//Just ignore sights when creating; these are sights of the actual creation ops.
break;
default:
S_LOG_WARNING("Unexpected state in state machine: " << m_state);
break;
};
}
EntityImporterBase::EntityImporterBase(const std::string& accountId, const std::string& avatarId) :
mAccountId(accountId), mAvatarId(avatarId), mStats( { }), m_state(INIT), mThoughtOpsInTransit(0), mSetOpsInTransit(0), mResumeWorld(0)
{
}
void EntityImporterBase::start(const std::string& filename)
{
S_LOG_VERBOSE("Starting import from " << filename);
auto factories = Atlas::Objects::Factories::instance();
auto rootObj = loadFromFile(filename);
if (!rootObj.isValid()) {
EventCompleted.emit();
return;
}
Atlas::Message::Element metaElem;
Atlas::Message::Element entitiesElem;
Atlas::Message::Element mindsElem;
Atlas::Message::Element rulesElem;
rootObj->copyAttr("meta", metaElem);
rootObj->copyAttr("entities", entitiesElem);
rootObj->copyAttr("minds", mindsElem);
if (rootObj->copyAttr("rules", rulesElem) == 0) {
if (!rulesElem.isList()) {
S_LOG_WARNING("Rules element is not list.");
EventCompleted.emit();
return;
} else {
for (auto& ruleMessage : rulesElem.asList()) {
if (ruleMessage.isMap()) {
auto object = factories->createObject(ruleMessage.asMap());
if (object.isValid()) {
if (!object->isDefaultId()) {
mPersistedRules.insert(std::make_pair(object->getId(), object));
}
}
}
}
}
}
if (!entitiesElem.isNone() && !entitiesElem.isList()) {
S_LOG_WARNING("Entities element is not list.");
EventCompleted.emit();
return;
}
if (!mindsElem.isNone() && !mindsElem.isList()) {
S_LOG_WARNING("Minds element is not list.");
EventCompleted.emit();
return;
}
if (!entitiesElem.isNone()) {
for (auto& entityMessage : entitiesElem.asList()) {
if (entityMessage.isMap()) {
auto& entityMap = entityMessage.asMap();
auto object = factories->createObject(entityMap);
if (object.isValid()) {
if (!object->isDefaultId()) {
registerEntityReferences(object->getId(), entityMap);
mPersistedEntities.insert(std::make_pair(object->getId(), object));
}
}
}
}
}
//If we should resume the world, check if the world has a "suspended" property,
//and disable it if so.
if (mResumeWorld) {
auto I = mPersistedEntities.find("0");
if (I != mPersistedEntities.end()) {
if (I->second->hasAttr("suspended")) {
I->second->setAttr("suspended", 0);
S_LOG_INFO("Resuming suspended world.");
}
}
}
if (!mindsElem.isNone()) {
for (auto& mindMessage : mindsElem.asList()) {
if (mindMessage.isMap()) {
auto object = factories->createObject(mindMessage.asMap());
if (object.isValid()) {
if (!object->isDefaultId()) {
mPersistedMinds.insert(std::make_pair(object->getId(), object));
}
}
}
}
}
S_LOG_INFO("Starting loading of world. Number of entities: " << mPersistedEntities.size() << " Number of minds: " << mPersistedMinds.size() << " Number of rules: " << mPersistedRules.size());
mStats.entitiesCount = static_cast<unsigned int>(mPersistedEntities.size());
mStats.mindsCount = static_cast<unsigned int>(mPersistedMinds.size());
mStats.rulesCount = static_cast<unsigned int>(mPersistedRules.size());
EventProgress.emit();
if (mPersistedRules.empty()) {
startEntityWalking();
} else {
startRuleWalking();
}
}
void EntityImporterBase::registerEntityReferences(const std::string& id, const Atlas::Message::MapType& element)
{
for (auto I : element) {
const auto& name = I.first;
if (name == "id" || name == "parent" || name == "contains") {
continue;
}
auto res = extractEntityReferences(I.second);
if (!res.empty()) {
mEntitiesWithReferenceAttributes[id].push_back({name, std::move(res)});
}
}
}
std::set<std::string> EntityImporterBase::extractEntityReferences(const Element& element) const
{
std::set<std::string> ids;
if (element.isMap()) {
auto entityRefI = element.asMap().find("$eid");
if (entityRefI != element.asMap().end() && entityRefI->second.isString()) {
ids.insert(entityRefI->second.String());
}
//If it's a map we need to process all child elements too
for (auto& I : element.asMap()) {
auto res = extractEntityReferences(I.second);
ids.insert(res.begin(), res.end());
}
} else if (element.isList()) {
//If it's a list we need to process all child elements too
for (auto& I : element.asList()) {
auto res = extractEntityReferences(I);
ids.insert(res.begin(), res.end());
}
}
return ids;
}
void EntityImporterBase::startEntityWalking()
{
m_state = ENTITY_WALKSTART;
Look l;
l->setFrom(mAvatarId);
l->setSerialno(newSerialNumber());
sendOperation(l);
}
void EntityImporterBase::startRuleWalking()
{
auto ruleI = mPersistedRules.find("root");
if (ruleI == mPersistedRules.end()) {
S_LOG_WARNING("Rules exist, but there's no root rule.");
startEntityWalking();
}
m_state = RULE_WALKING;
OpVector res;
getRule("root", res);
for (auto& op : res) {
sendOperation(op);
}
}
void EntityImporterBase::sendOperation(const Operation& op)
{
//std::cout << "Sending op ============";
//debug_dump(op, std::cout);
if (!op->isDefaultSerialno()) {
sigc::slot<void, const Operation&> slot = sigc::mem_fun(*this, &EntityImporterBase::operation);
sendAndAwaitResponse(op, slot);
} else {
send(op);
}
}
void EntityImporterBase::cancel()
{
m_state = CANCEL;
}
const EntityImporterBase::Stats& EntityImporterBase::getStats() const
{
return mStats;
}
void EntityImporterBase::setResume(bool enabled)
{
mResumeWorld = enabled;
}
void EntityImporterBase::operationThinkResult(const Operation & op)
{
mThoughtOpsInTransit--;
if (mThoughtOpsInTransit == 0) {
complete();
}
}
void EntityImporterBase::operationSetResult(const Operation & op)
{
mSetOpsInTransit--;
if (mSetOpsInTransit == 0) {
sendMinds();
}
}
void EntityImporterBase::operation(const Operation & op)
{
//std::cout << "Got op ==================" << std::endl;
//debug_dump(op, std::cout);
if (m_state == CANCEL) {
m_state = CANCELLED;
return;
}
if (m_state == CANCELLED) {
return;
}
OpVector res;
if (op->getClassNo() == Atlas::Objects::Operation::INFO_NO) {
infoArrived(op, res);
} else if (op->getClassNo() == Atlas::Objects::Operation::ERROR_NO) {
errorArrived(op, res);
} else if (op->getClassNo() == Atlas::Objects::Operation::SIGHT_NO) {
sightArrived(op, res);
}
for (auto& resOp : res) {
sendOperation(resOp);
}
}