blob: 71235abfc0ad1d5955ef8c665d530e8fb2eb821a [file] [log] [blame]
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/shared/starboard/application.h"
#include <string>
#include "starboard/atomic.h"
#include "starboard/common/scoped_ptr.h"
#include "starboard/condition_variable.h"
#include "starboard/event.h"
#include "starboard/log.h"
#include "starboard/memory.h"
#include "starboard/string.h"
#include "starboard/shared/starboard/command_line.h"
namespace starboard {
namespace shared {
namespace starboard {
namespace {
const char kPreloadSwitch[] = "preload";
const char kLinkSwitch[] = "link";
// Dispatches an event of |type| with |data| to the system event handler,
// calling |destructor| on |data| when finished dispatching. Does all
// appropriate NULL checks so you don't have to.
void Dispatch(SbEventType type, void* data, SbEventDataDestructor destructor) {
SbEvent event;
event.type = type;
event.data = data;
SbEventHandle(&event);
if (destructor) {
destructor(event.data);
}
}
} // namespace
// The next event ID to use for Schedule().
volatile SbAtomic32 g_next_event_id = 0;
Application* Application::g_instance = NULL;
Application::Application()
: error_level_(0),
thread_(SbThreadGetCurrent()),
start_link_(NULL),
state_(kStateUnstarted) {
Application* old_instance =
reinterpret_cast<Application*>(SbAtomicAcquire_CompareAndSwapPtr(
reinterpret_cast<SbAtomicPtr*>(&g_instance),
reinterpret_cast<SbAtomicPtr>(reinterpret_cast<void*>(NULL)),
reinterpret_cast<SbAtomicPtr>(this)));
SB_DCHECK(!old_instance);
SB_UNREFERENCED_PARAMETER(old_instance);
}
Application::~Application() {
Application* old_instance =
reinterpret_cast<Application*>(SbAtomicAcquire_CompareAndSwapPtr(
reinterpret_cast<SbAtomicPtr*>(&g_instance),
reinterpret_cast<SbAtomicPtr>(this),
reinterpret_cast<SbAtomicPtr>(reinterpret_cast<void*>(NULL))));
SB_DCHECK(old_instance);
SB_DCHECK(old_instance == this);
SbMemoryDeallocate(start_link_);
}
int Application::Run(int argc, char** argv, const char* link_data) {
Initialize();
command_line_.reset(new CommandLine(argc, argv));
if (link_data) {
SetStartLink(link_data);
}
return RunLoop();
}
int Application::Run(int argc, char** argv) {
Initialize();
command_line_.reset(new CommandLine(argc, argv));
if (command_line_->HasSwitch(kLinkSwitch)) {
std::string value = command_line_->GetSwitchValue(kLinkSwitch);
if (!value.empty()) {
SetStartLink(value.c_str());
}
}
return RunLoop();
}
CommandLine* Application::GetCommandLine() {
return command_line_.get();
}
void Application::Pause(void* context, EventHandledCallback callback) {
Inject(new Event(kSbEventTypePause, context, callback));
}
void Application::Unpause(void* context, EventHandledCallback callback) {
Inject(new Event(kSbEventTypeUnpause, context, callback));
}
void Application::Suspend(void* context, EventHandledCallback callback) {
Inject(new Event(kSbEventTypeSuspend, context, callback));
}
void Application::Resume(void* context, EventHandledCallback callback) {
Inject(new Event(kSbEventTypeResume, context, callback));
}
void Application::Stop(int error_level) {
Event* event = new Event(kSbEventTypeStop, NULL, NULL);
event->error_level = error_level;
Inject(event);
}
void Application::Link(const char *link_data) {
SB_DCHECK(link_data) << "You must call Link with link_data.";
Inject(new Event(kSbEventTypeLink, SbStringDuplicate(link_data),
SbMemoryDeallocate));
}
void Application::InjectLowMemoryEvent() {
#if SB_API_VERSION >= SB_LOW_MEMORY_EVENT_API_VERSION
Inject(new Event(kSbEventTypeLowMemory, NULL, NULL));
#endif // SB_API_VERSION >= SB_LOW_MEMORY_EVENT_API_VERSION
}
SbEventId Application::Schedule(SbEventCallback callback,
void* context,
SbTimeMonotonic delay) {
SbEventId id = SbAtomicNoBarrier_Increment(&g_next_event_id, 1);
InjectTimedEvent(new TimedEvent(id, callback, context, delay));
return id;
}
void Application::Cancel(SbEventId id) {
CancelTimedEvent(id);
}
#if SB_HAS(PLAYER)
void Application::HandleFrame(SbPlayer player,
const scoped_refptr<VideoFrame>& frame,
int z_index,
int x,
int y,
int width,
int height) {
AcceptFrame(player, frame, z_index, x, y, width, height);
}
#endif // SB_HAS(PLAYER)
void Application::SetStartLink(const char* start_link) {
SB_DCHECK(IsCurrentThread());
SbMemoryDeallocate(start_link_);
if (start_link) {
start_link_ = SbStringDuplicate(start_link);
} else {
start_link_ = NULL;
}
}
void Application::DispatchStart() {
SB_DCHECK(IsCurrentThread());
SB_DCHECK(state_ == kStateUnstarted || state_ == kStatePreloading);
DispatchAndDelete(CreateInitialEvent(kSbEventTypeStart));
}
void Application::DispatchPreload() {
SB_DCHECK(IsCurrentThread());
#if SB_API_VERSION >= SB_PRELOAD_API_VERSION
SB_DCHECK(state_ == kStateUnstarted);
DispatchAndDelete(CreateInitialEvent(kSbEventTypePreload));
#else // SB_API_VERSION >= SB_PRELOAD_API_VERSION
SB_NOTREACHED();
#endif // SB_API_VERSION >= SB_PRELOAD_API_VERSION
}
bool Application::HasPreloadSwitch() {
return command_line_->HasSwitch(kPreloadSwitch);
}
bool Application::DispatchAndDelete(Application::Event* event) {
SB_DCHECK(IsCurrentThread());
if (!event) {
return true;
}
// Ensure the event is deleted unless it is released.
scoped_ptr<Event> scoped_event(event);
// Ensure that we go through the the appropriate lifecycle events based on the
// current state.
switch (scoped_event->event->type) {
#if SB_API_VERSION >= SB_PRELOAD_API_VERSION
case kSbEventTypePreload:
if (state() != kStateUnstarted) {
return true;
}
break;
#endif // SB_API_VERSION >= SB_PRELOAD_API_VERSION
case kSbEventTypeStart:
if (state() != kStatePreloading && state() != kStateUnstarted) {
return true;
}
break;
case kSbEventTypePause:
if (state() != kStateStarted) {
return true;
}
break;
case kSbEventTypeUnpause:
if (state() == kStateStarted) {
return true;
}
if (state() == kStatePreloading) {
// Convert to Start event and consume.
DispatchStart();
return true;
}
if (state() == kStateSuspended) {
Inject(new Event(kSbEventTypeResume, NULL, NULL));
Inject(scoped_event.release());
return true;
}
break;
case kSbEventTypeSuspend:
if (state() == kStateSuspended) {
return true;
}
if (state() == kStatePreloading) {
// If Preloading, we can jump straight to Suspended, so we don't try to
// do anything fancy here.
break;
}
if (state() == kStateStarted) {
Inject(new Event(kSbEventTypePause, NULL, NULL));
Inject(scoped_event.release());
return true;
}
break;
case kSbEventTypeResume:
if (state() == kStateStarted || state() == kStatePaused) {
return true;
}
if (state() == kStateSuspended) {
OnResume();
}
break;
case kSbEventTypeStop:
if (state() == kStateStarted) {
Inject(new Event(kSbEventTypePause, NULL, NULL));
Inject(new Event(kSbEventTypeSuspend, NULL, NULL));
Inject(scoped_event.release());
return true;
}
if (state() == kStatePaused) {
Inject(new Event(kSbEventTypeSuspend, NULL, NULL));
Inject(scoped_event.release());
return true;
}
error_level_ = scoped_event->error_level;
break;
case kSbEventTypeScheduled: {
TimedEvent* timed_event =
reinterpret_cast<TimedEvent*>(scoped_event->event->data);
timed_event->callback(timed_event->context);
return true;
}
default:
break;
}
SbEventHandle(scoped_event->event);
switch (scoped_event->event->type) {
#if SB_API_VERSION >= SB_PRELOAD_API_VERSION
case kSbEventTypePreload:
SB_DCHECK(state() == kStateUnstarted);
state_ = kStatePreloading;
break;
#endif // SB_API_VERSION >= SB_PRELOAD_API_VERSION
case kSbEventTypeStart:
SB_DCHECK(state() == kStatePreloading || state() == kStateUnstarted);
state_ = kStateStarted;
break;
case kSbEventTypePause:
SB_DCHECK(state() == kStateStarted);
state_ = kStatePaused;
break;
case kSbEventTypeUnpause:
SB_DCHECK(state() == kStatePaused);
state_ = kStateStarted;
break;
case kSbEventTypeSuspend:
SB_DCHECK(state() == kStatePreloading || state() == kStatePaused);
state_ = kStateSuspended;
OnSuspend();
break;
case kSbEventTypeResume:
SB_DCHECK(state() == kStateSuspended);
state_ = kStatePaused;
break;
case kSbEventTypeStop:
SB_DCHECK(state() == kStateSuspended);
state_ = kStateStopped;
return false;
default:
break;
}
// Should not be unstarted after the first event.
SB_DCHECK(state() != kStateUnstarted);
return true;
}
void Application::CallTeardownCallbacks() {
ScopedLock lock(callbacks_lock_);
for (size_t i = 0; i < teardown_callbacks_.size(); ++i) {
teardown_callbacks_[i]();
}
}
Application::Event* Application::CreateInitialEvent(SbEventType type) {
#if SB_API_VERSION >= SB_PRELOAD_API_VERSION
SB_DCHECK(type == kSbEventTypePreload || type == kSbEventTypeStart);
#else // SB_API_VERSION >= SB_PRELOAD_API_VERSION
SB_DCHECK(type == kSbEventTypeStart);
#endif // SB_API_VERSION >= SB_PRELOAD_API_VERSION
SbEventStartData* start_data = new SbEventStartData();
SbMemorySet(start_data, 0, sizeof(SbEventStartData));
start_data->argument_values =
const_cast<char**>(command_line_->GetOriginalArgv());
start_data->argument_count = command_line_->GetOriginalArgc();
start_data->link = start_link_;
return new Event(type, start_data, &DeleteDestructor<SbEventStartData>);
}
int Application::RunLoop() {
SB_DCHECK(command_line_);
if (IsPreloadImmediate()) {
DispatchPreload();
} else if (IsStartImmediate()) {
DispatchStart();
}
for (;;) {
if (!DispatchNextEvent()) {
break;
}
}
CallTeardownCallbacks();
Teardown();
return error_level_;
}
} // namespace starboard
} // namespace shared
} // namespace starboard