blob: cd0bc90ed39adebdcf43e10392deeed69afc7afd [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 "src/ui/scenic/lib/scenic/session.h"
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <trace/event.h>
namespace scenic_impl {
Session::Session(SessionId id, fidl::InterfaceRequest<fuchsia::ui::scenic::Session> session_request,
fidl::InterfaceHandle<fuchsia::ui::scenic::SessionListener> listener)
: id_(id),
listener_(listener.Bind()),
reporter_(std::make_shared<EventAndErrorReporter>(this)),
binding_(this, std::move(session_request)),
weak_factory_(this) {
FXL_DCHECK(!binding_.channel() || binding_.is_bound());
}
Session::~Session() {
valid_ = false;
reporter_->Reset();
}
void Session::set_binding_error_handler(fit::function<void(zx_status_t)> error_handler) {
binding_.set_error_handler(std::move(error_handler));
}
void Session::SetFrameScheduler(
const std::shared_ptr<scheduling::FrameScheduler>& frame_scheduler) {
FXL_DCHECK(frame_scheduler_.expired()) << "Error: FrameScheduler already set";
frame_scheduler_ = frame_scheduler;
// Initialize OnFramePresented callback.
// Check validity because it's not always set in tests.
if (frame_scheduler)
frame_scheduler->SetOnFramePresentedCallbackForSession(
id_,
[weak = weak_factory_.GetWeakPtr()](fuchsia::scenic::scheduling::FramePresentedInfo info) {
if (!weak->binding_.is_bound())
return;
// Update and set num_presents_allowed before ultimately calling into the client provided
// callback.
weak->num_presents_allowed_ += (info.presentation_infos.size());
FXL_DCHECK(weak->num_presents_allowed_ <= kMaxPresentsInFlight);
info.num_presents_allowed = weak->num_presents_allowed_;
weak->binding_.events().OnFramePresented(std::move(info));
});
}
void Session::Enqueue(std::vector<fuchsia::ui::scenic::Command> cmds) {
// TODO(SCN-1265): Come up with a better solution to avoid children
// calling into us during destruction.
if (!valid_)
return;
TRACE_DURATION("gfx", "scenic_impl::Session::Enqueue", "session_id", id(), "num_commands", cmds.size());
for (auto& cmd : cmds) {
// TODO(SCN-710): This dispatch is far from optimal in terms of performance.
// We need to benchmark it to figure out whether it matters.
System::TypeId type_id = SystemTypeForCmd(cmd);
auto dispatcher = type_id != System::TypeId::kInvalid ? dispatchers_[type_id].get() : nullptr;
if (dispatcher) {
dispatcher->DispatchCommand(std::move(cmd));
} else {
reporter_->EnqueueEvent(std::move(cmd));
}
}
}
void Session::Present(uint64_t presentation_time, std::vector<zx::event> acquire_fences,
std::vector<zx::event> release_fences, PresentCallback callback) {
TRACE_DURATION("gfx", "scenic_impl::Session::Present");
TRACE_FLOW_END("gfx", "Session::Present", next_present_trace_id_);
next_present_trace_id_++;
// TODO(SCN-1265): Come up with a better solution to avoid children
// calling into us during destruction.
if (!valid_)
return;
// TODO(SCN-469): Move Present logic into Session.
GetTempSessionDelegate()->Present(presentation_time, std::move(acquire_fences),
std::move(release_fences), std::move(callback));
}
void Session::Present2(fuchsia::ui::scenic::Present2Args args, Present2Callback callback) {
TRACE_DURATION("gfx", "scenic_impl::Session::Present2");
TRACE_FLOW_END("gfx", "Session::Present", next_present_trace_id_);
next_present_trace_id_++;
if (!valid_)
return;
// Kill the Session if they have not set any of the Present2Args fields.
if (!args.has_requested_presentation_time() || !args.has_release_fences() ||
!args.has_acquire_fences() || !args.has_requested_prediction_span()) {
FXL_LOG(ERROR) << "One or more fields not set in Present2Args table";
valid_ = false;
GetTempSessionDelegate()->KillSession();
return;
}
// Kill the Session if they have no more presents left.
if (--num_presents_allowed_ < 0) {
FXL_LOG(ERROR) << "No presents left";
valid_ = false;
GetTempSessionDelegate()->KillSession();
return;
}
// After decrementing |num_presents_allowed_|, fire the immediate callback.
InvokeFuturePresentationTimesCallback(args.requested_prediction_span(), std::move(callback));
// Schedule the update.
GetTempSessionDelegate()->Present2(args.requested_presentation_time(),
std::move(*args.mutable_acquire_fences()),
std::move(*args.mutable_release_fences()));
}
void Session::RequestPresentationTimes(zx_duration_t requested_prediction_span,
RequestPresentationTimesCallback callback) {
TRACE_DURATION("gfx", "scenic_impl::Session::RequestPresentationTimes");
InvokeFuturePresentationTimesCallback(requested_prediction_span, std::move(callback));
}
void Session::InvokeFuturePresentationTimesCallback(zx_duration_t requested_prediction_span,
RequestPresentationTimesCallback callback) {
if (callback) {
GetTempSessionDelegate()->GetFuturePresentationInfos(
zx::duration(requested_prediction_span),
[weak = weak_factory_.GetWeakPtr(), callback = std::move(callback)](
std::vector<fuchsia::scenic::scheduling::PresentationInfo> presentation_infos) {
callback({std::move(presentation_infos), weak ? weak->num_presents_allowed_ : 0});
});
}
}
void Session::SetCommandDispatchers(
std::array<CommandDispatcherUniquePtr, System::TypeId::kMaxSystems> dispatchers) {
for (size_t i = 0; i < System::TypeId::kMaxSystems; ++i) {
dispatchers_[i] = std::move(dispatchers[i]);
}
}
void Session::SetDebugName(std::string debug_name) {
// TODO(SCN-1265): Come up with a better solution to avoid children
// calling into us during destruction.
if (!valid_)
return;
TRACE_DURATION("gfx", "scenic_impl::Session::SetDebugName");
GetTempSessionDelegate()->SetDebugName(debug_name);
}
Session::EventAndErrorReporter::EventAndErrorReporter(Session* session)
: session_(session), weak_factory_(this) {
FXL_DCHECK(session_);
}
void Session::EventAndErrorReporter::Reset() { session_ = nullptr; }
void Session::EventAndErrorReporter::PostFlushTask() {
FXL_DCHECK(session_);
TRACE_DURATION("gfx", "scenic_impl::Session::EventAndErrorReporter::PostFlushTask");
// If this is the first EnqueueEvent() since the last FlushEvent(), post a
// task to ensure that FlushEvents() is called.
if (buffered_events_.empty()) {
async::PostTask(async_get_default_dispatcher(),
[shared_this = session_->reporter_] { shared_this->FlushEvents(); });
}
}
void Session::EventAndErrorReporter::EnqueueEvent(fuchsia::ui::gfx::Event event) {
if (!session_)
return;
TRACE_DURATION("gfx", "scenic_impl::Session::EventAndErrorReporter::EnqueueEvent", "event_type", "gfx::Event");
PostFlushTask();
fuchsia::ui::scenic::Event scenic_event;
scenic_event.set_gfx(std::move(event));
buffered_events_.push_back(std::move(scenic_event));
}
void Session::EventAndErrorReporter::EnqueueEvent(fuchsia::ui::scenic::Command unhandled_command) {
if (!session_)
return;
TRACE_DURATION("gfx", "scenic_impl::Session::EventAndErrorReporter::EnqueueEvent", "event_type", "UnhandledCommand");
PostFlushTask();
fuchsia::ui::scenic::Event scenic_event;
scenic_event.set_unhandled(std::move(unhandled_command));
buffered_events_.push_back(std::move(scenic_event));
}
void Session::EventAndErrorReporter::EnqueueEvent(fuchsia::ui::input::InputEvent event) {
if (!session_)
return;
TRACE_DURATION("gfx", "scenic_impl::Session::EventAndErrorReporter::EnqueueEvent", "event_type", "input::InputEvent");
// Force an immediate flush, preserving event order.
fuchsia::ui::scenic::Event scenic_event;
scenic_event.set_input(std::move(event));
buffered_events_.push_back(std::move(scenic_event));
FlushEvents();
}
void Session::EventAndErrorReporter::FlushEvents() {
if (!session_)
return;
TRACE_DURATION("gfx", "scenic_impl::Session::EventAndErrorReporter::FlushEvents");
if (!buffered_events_.empty()) {
if (session_->listener_) {
session_->listener_->OnScenicEvent(std::move(buffered_events_));
} else if (event_callback_) {
// Only use the callback if there is no listener. It is difficult to do
// better because we std::move the argument into OnScenicEvent().
for (auto& evt : buffered_events_) {
event_callback_(std::move(evt));
}
}
buffered_events_.clear();
}
}
void Session::EventAndErrorReporter::ReportError(fxl::LogSeverity severity,
std::string error_string) {
// TODO(SCN-1265): Come up with a better solution to avoid children
// calling into us during destruction.
if (!session_) {
FXL_LOG(ERROR) << "Reporting Scenic Session error after session destroyed: " << error_string;
return;
}
TRACE_DURATION("gfx", "scenic_impl::Session::EventAndErrorReporter::ReportError");
switch (severity) {
case fxl::LOG_INFO:
FXL_LOG(INFO) << error_string;
return;
case fxl::LOG_WARNING:
FXL_LOG(WARNING) << error_string;
return;
case fxl::LOG_ERROR:
FXL_LOG(ERROR) << "Scenic session error (session_id: " << session_->id()
<< "): " << error_string;
if (error_callback_) {
error_callback_(error_string);
}
if (session_->listener_) {
session_->listener_->OnScenicError(std::move(error_string));
}
return;
case fxl::LOG_FATAL:
FXL_LOG(FATAL) << error_string;
return;
default:
// Invalid severity.
FXL_DCHECK(false);
}
}
TempSessionDelegate* Session::GetTempSessionDelegate() {
auto& dispatcher = dispatchers_[System::TypeId::kGfx];
return dispatcher ? static_cast<TempSessionDelegate*>(dispatcher.get()) : nullptr;
}
} // namespace scenic_impl