blob: 886b5fdad1785584071144a258c61055eff91a74 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "mojo/services/public/cpp/view_manager/lib/view_manager_synchronizer.h"
#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "mojo/public/cpp/shell/service.h"
#include "mojo/public/interfaces/shell/shell.mojom.h"
#include "mojo/services/public/cpp/view_manager/lib/view_manager_private.h"
#include "mojo/services/public/cpp/view_manager/lib/view_tree_node_private.h"
#include "mojo/services/public/cpp/view_manager/util.h"
namespace mojo {
namespace services {
namespace view_manager {
TransportNodeId MakeTransportNodeId(uint16_t connection_id,
uint16_t local_node_id) {
return (connection_id << 16) | local_node_id;
}
class ViewManagerTransaction {
public:
virtual ~ViewManagerTransaction() {}
void Commit() {
DCHECK(!committed_);
DoCommit();
committed_ = true;
}
bool committed() const { return committed_; }
uint32_t change_id() const { return change_id_; }
// General callback to be used for commits to the service.
void OnActionCompleted(bool success) {
DCHECK(success);
DoActionCompleted(success);
synchronizer_->RemoveFromPendingQueue(this);
}
protected:
enum TransactionType {
// Node creation and destruction.
TYPE_CREATE_VIEW_TREE_NODE,
TYPE_DESTROY_VIEW_TREE_NODE,
// Modifications to the hierarchy (addition of or removal of nodes from a
// parent.)
TYPE_HIERARCHY
};
ViewManagerTransaction(TransactionType transaction_type,
ViewManagerSynchronizer* synchronizer)
: transaction_type_(transaction_type),
change_id_(synchronizer->GetNextChangeId()),
committed_(false),
synchronizer_(synchronizer) {
}
// Overridden to perform transaction-specific commit actions.
virtual void DoCommit() = 0;
// Overridden to perform transaction-specific cleanup on commit ack from the
// service.
virtual void DoActionCompleted(bool success) = 0;
IViewManager* service() { return synchronizer_->service_.get(); }
private:
const TransactionType transaction_type_;
const uint32_t change_id_;
bool committed_;
ViewManagerSynchronizer* synchronizer_;
DISALLOW_COPY_AND_ASSIGN(ViewManagerTransaction);
};
class CreateViewTreeNodeTransaction : public ViewManagerTransaction {
public:
CreateViewTreeNodeTransaction(uint16_t node_id,
ViewManagerSynchronizer* synchronizer)
: ViewManagerTransaction(TYPE_CREATE_VIEW_TREE_NODE, synchronizer),
node_id_(node_id) {}
virtual ~CreateViewTreeNodeTransaction() {}
private:
// Overridden from ViewManagerTransaction:
virtual void DoCommit() OVERRIDE {
service()->CreateNode(
node_id_,
base::Bind(&ViewManagerTransaction::OnActionCompleted,
base::Unretained(this)));
}
virtual void DoActionCompleted(bool success) OVERRIDE {
// TODO(beng): Failure means we tried to create with an extant id for this
// connection. Figure out what to do.
}
const uint16_t node_id_;
DISALLOW_COPY_AND_ASSIGN(CreateViewTreeNodeTransaction);
};
class DestroyViewTreeNodeTransaction : public ViewManagerTransaction {
public:
DestroyViewTreeNodeTransaction(TransportNodeId node_id,
ViewManagerSynchronizer* synchronizer)
: ViewManagerTransaction(TYPE_DESTROY_VIEW_TREE_NODE, synchronizer),
node_id_(node_id) {}
virtual ~DestroyViewTreeNodeTransaction() {}
private:
// Overridden from ViewManagerTransaction:
virtual void DoCommit() OVERRIDE {
service()->DeleteNode(
node_id_,
change_id(),
base::Bind(&ViewManagerTransaction::OnActionCompleted,
base::Unretained(this)));
}
virtual void DoActionCompleted(bool success) OVERRIDE {
// TODO(beng): recovery?
}
TransportNodeId node_id_;
DISALLOW_COPY_AND_ASSIGN(DestroyViewTreeNodeTransaction);
};
class HierarchyTransaction : public ViewManagerTransaction {
public:
enum HierarchyChangeType {
TYPE_ADD,
TYPE_REMOVE
};
HierarchyTransaction(HierarchyChangeType hierarchy_change_type,
TransportNodeId child_id,
TransportNodeId parent_id,
ViewManagerSynchronizer* synchronizer)
: ViewManagerTransaction(TYPE_HIERARCHY, synchronizer),
hierarchy_change_type_(hierarchy_change_type),
child_id_(child_id),
parent_id_(parent_id) {}
virtual ~HierarchyTransaction() {}
private:
// Overridden from ViewManagerTransaction:
virtual void DoCommit() OVERRIDE {
switch (hierarchy_change_type_) {
case TYPE_ADD:
service()->AddNode(
parent_id_,
child_id_,
change_id(),
base::Bind(&ViewManagerTransaction::OnActionCompleted,
base::Unretained(this)));
break;
case TYPE_REMOVE:
service()->RemoveNodeFromParent(
child_id_,
change_id(),
base::Bind(&ViewManagerTransaction::OnActionCompleted,
base::Unretained(this)));
break;
}
}
virtual void DoActionCompleted(bool success) OVERRIDE {
// TODO(beng): Failure means either one of the nodes specified didn't exist,
// or we passed the same node id for both params. Roll back?
}
const HierarchyChangeType hierarchy_change_type_;
const TransportNodeId child_id_;
const TransportNodeId parent_id_;
DISALLOW_COPY_AND_ASSIGN(HierarchyTransaction);
};
ViewManagerSynchronizer::ViewManagerSynchronizer(ViewManager* view_manager)
: view_manager_(view_manager),
connected_(false),
connection_id_(0),
next_id_(1),
next_change_id_(0),
sync_factory_(this),
init_loop_(NULL) {
ConnectTo(ViewManagerPrivate(view_manager_).shell(), "mojo:mojo_view_manager",
&service_);
service_->SetClient(this);
AllocationScope scope;
service_->GetNodeTree(
1,
base::Bind(&ViewManagerSynchronizer::OnRootTreeReceived,
base::Unretained(this)));
base::RunLoop loop;
init_loop_ = &loop;
init_loop_->Run();
init_loop_ = NULL;
}
ViewManagerSynchronizer::~ViewManagerSynchronizer() {
DoSync();
}
TransportNodeId ViewManagerSynchronizer::CreateViewTreeNode() {
DCHECK(connected_);
uint16_t id = ++next_id_;
pending_transactions_.push_back(new CreateViewTreeNodeTransaction(id, this));
ScheduleSync();
return MakeTransportNodeId(connection_id_, id);
}
void ViewManagerSynchronizer::DestroyViewTreeNode(TransportNodeId node_id) {
DCHECK(connected_);
pending_transactions_.push_back(
new DestroyViewTreeNodeTransaction(node_id, this));
ScheduleSync();
}
void ViewManagerSynchronizer::AddChild(TransportNodeId child_id,
TransportNodeId parent_id) {
DCHECK(connected_);
pending_transactions_.push_back(
new HierarchyTransaction(HierarchyTransaction::TYPE_ADD,
child_id,
parent_id,
this));
ScheduleSync();
}
void ViewManagerSynchronizer::RemoveChild(TransportNodeId child_id,
TransportNodeId parent_id) {
DCHECK(connected_);
pending_transactions_.push_back(
new HierarchyTransaction(HierarchyTransaction::TYPE_REMOVE,
child_id,
parent_id,
this));
ScheduleSync();
}
bool ViewManagerSynchronizer::OwnsNode(TransportNodeId id) const {
return HiWord(id) == connection_id_;
}
////////////////////////////////////////////////////////////////////////////////
// ViewManagerSynchronizer, IViewManagerClient implementation:
void ViewManagerSynchronizer::OnConnectionEstablished(uint16 connection_id) {
connected_ = true;
connection_id_ = connection_id;
ScheduleSync();
}
void ViewManagerSynchronizer::OnNodeHierarchyChanged(uint32_t node_id,
uint32_t new_parent_id,
uint32_t old_parent_id,
uint32_t change_id) {
if (change_id == 0) {
ViewTreeNode* new_parent = view_manager_->GetNodeById(new_parent_id);
ViewTreeNode* old_parent = view_manager_->GetNodeById(old_parent_id);
ViewTreeNode* node = NULL;
if (old_parent) {
// Existing node, mapped in this connection's tree.
// TODO(beng): verify this is actually true.
node = view_manager_->GetNodeById(node_id);
DCHECK_EQ(node->parent(), old_parent);
} else {
// New node, originating from another connection.
node = ViewTreeNodePrivate::LocalCreate();
ViewTreeNodePrivate private_node(node);
private_node.set_view_manager(view_manager_);
private_node.set_id(node_id);
ViewManagerPrivate(view_manager_).AddNode(node->id(), node);
}
if (new_parent)
ViewTreeNodePrivate(new_parent).LocalAddChild(node);
else
ViewTreeNodePrivate(old_parent).LocalRemoveChild(node);
}
}
void ViewManagerSynchronizer::OnNodeViewReplaced(uint32_t node,
uint32_t new_view_id,
uint32_t old_view_id,
uint32_t change_id) {
// ..
}
void ViewManagerSynchronizer::OnNodeDeleted(uint32_t node_id,
uint32_t change_id) {
if (change_id == 0) {
ViewTreeNode* node = view_manager_->GetNodeById(node_id);
if (node)
ViewTreeNodePrivate(node).LocalDestroy();
}
}
////////////////////////////////////////////////////////////////////////////////
// ViewManagerSynchronizer, private:
void ViewManagerSynchronizer::ScheduleSync() {
if (sync_factory_.HasWeakPtrs())
return;
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&ViewManagerSynchronizer::DoSync, sync_factory_.GetWeakPtr()));
}
void ViewManagerSynchronizer::DoSync() {
// The service connection may not be set up yet. OnConnectionEstablished()
// will schedule another sync when it is.
if (!connected_)
return;
Transactions::const_iterator it = pending_transactions_.begin();
for (; it != pending_transactions_.end(); ++it) {
if (!(*it)->committed())
(*it)->Commit();
}
}
uint32_t ViewManagerSynchronizer::GetNextChangeId() {
// TODO(beng): deal with change id collisions? Important in the "never ack'ed
// change" case mentioned in OnNodeHierarchyChanged().
// "0" is a special value passed to other connected clients, so we can't use
// it.
next_change_id_ = std::max(1u, next_change_id_ + 1);
return next_change_id_;
}
void ViewManagerSynchronizer::RemoveFromPendingQueue(
ViewManagerTransaction* transaction) {
DCHECK_EQ(transaction, pending_transactions_.front());
pending_transactions_.erase(pending_transactions_.begin());
}
void ViewManagerSynchronizer::OnRootTreeReceived(
const Array<INode>& nodes) {
std::vector<ViewTreeNode*> parents;
ViewTreeNode* root = NULL;
ViewTreeNode* last_node = NULL;
for (size_t i = 0; i < nodes.size(); ++i) {
if (last_node && nodes[i].parent_id() == last_node->id()) {
parents.push_back(last_node);
} else if (!parents.empty()) {
while (parents.back()->id() != nodes[i].parent_id())
parents.pop_back();
}
// We don't use the ctor that takes a ViewManager here, since it will call
// back to the service and attempt to create a new node.
ViewTreeNode* node = ViewTreeNodePrivate::LocalCreate();
ViewTreeNodePrivate private_node(node);
private_node.set_view_manager(view_manager_);
private_node.set_id(nodes[i].node_id());
if (!parents.empty())
ViewTreeNodePrivate(parents.back()).LocalAddChild(node);
if (!last_node)
root = node;
last_node = node;
ViewManagerPrivate(view_manager_).AddNode(node->id(), node);
}
ViewManagerPrivate(view_manager_).set_root(root);
if (init_loop_)
init_loop_->Quit();
}
} // namespace view_manager
} // namespace services
} // namespace mojo