/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you 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 . */ #include "PresenterTimer.hxx" #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; using namespace ::com::sun::star::uno; namespace sdext::presenter { namespace { class TimerTask { public: TimerTask ( const PresenterTimer::Task& rTask, const TimeValue& rDueTime, const sal_Int64 nRepeatInterval, const sal_Int32 nTaskId); PresenterTimer::Task maTask; TimeValue maDueTime; const sal_Int64 mnRepeatInterval; const sal_Int32 mnTaskId; bool mbIsCanceled; }; typedef std::shared_ptr SharedTimerTask; class TimerTaskComparator { public: bool operator() (const SharedTimerTask& rpTask1, const SharedTimerTask& rpTask2) const { return rpTask1->maDueTime.Seconds < rpTask2->maDueTime.Seconds || (rpTask1->maDueTime.Seconds == rpTask2->maDueTime.Seconds && rpTask1->maDueTime.Nanosec < rpTask2->maDueTime.Nanosec); } }; /** Queue all scheduled tasks and process them when their time has come. */ class TimerScheduler : public std::enable_shared_from_this, public ::osl::Thread { public: static std::shared_ptr Instance( uno::Reference const& xContext); static SharedTimerTask CreateTimerTask ( const PresenterTimer::Task& rTask, const TimeValue& rDueTime, const sal_Int64 nRepeatInterval); void ScheduleTask (const SharedTimerTask& rpTask); void CancelTask (const sal_Int32 nTaskId); static bool GetCurrentTime (TimeValue& rCurrentTime); static sal_Int64 GetTimeDifference ( const TimeValue& rTargetTime, const TimeValue& rCurrentTime); static void ConvertToTimeValue ( TimeValue& rTimeValue, const sal_Int64 nTimeDifference); static sal_Int64 ConvertFromTimeValue ( const TimeValue& rTimeValue); static void NotifyTermination(); #if !defined NDEBUG static bool HasInstance() { return mpInstance != nullptr; } #endif private: static std::shared_ptr mpInstance; static std::mutex maInstanceMutex; std::shared_ptr mpLateDestroy; // for clean exit static sal_Int32 mnTaskId; std::mutex maTaskContainerMutex; typedef ::std::set TaskContainer; TaskContainer maScheduledTasks; std::mutex maCurrentTaskMutex; SharedTimerTask mpCurrentTask; ::osl::Condition m_Shutdown; TimerScheduler( uno::Reference const& xContext); public: virtual void SAL_CALL run() override; virtual void SAL_CALL onTerminated() override { mpLateDestroy.reset(); } }; class TerminateListener : public ::cppu::WeakImplHelper { virtual ~TerminateListener() override { assert(!TimerScheduler::HasInstance()); } virtual void SAL_CALL disposing(lang::EventObject const&) override { } virtual void SAL_CALL queryTermination(lang::EventObject const&) override { } virtual void SAL_CALL notifyTermination(lang::EventObject const&) override { TimerScheduler::NotifyTermination(); } }; } // end of anonymous namespace //===== PresenterTimer ======================================================== sal_Int32 PresenterTimer::ScheduleRepeatedTask ( const uno::Reference& xContext, const Task& rTask, const sal_Int64 nDelay, const sal_Int64 nInterval) { assert(xContext.is()); TimeValue aCurrentTime; if (TimerScheduler::GetCurrentTime(aCurrentTime)) { TimeValue aDueTime; TimerScheduler::ConvertToTimeValue( aDueTime, TimerScheduler::ConvertFromTimeValue (aCurrentTime) + nDelay); SharedTimerTask pTask (TimerScheduler::CreateTimerTask(rTask, aDueTime, nInterval)); TimerScheduler::Instance(xContext)->ScheduleTask(pTask); return pTask->mnTaskId; } return NotAValidTaskId; } void PresenterTimer::CancelTask (const sal_Int32 nTaskId) { auto const pInstance(TimerScheduler::Instance(nullptr)); if (pInstance) { pInstance->CancelTask(nTaskId); } } //===== TimerScheduler ======================================================== std::shared_ptr TimerScheduler::mpInstance; std::mutex TimerScheduler::maInstanceMutex; sal_Int32 TimerScheduler::mnTaskId = PresenterTimer::NotAValidTaskId; std::shared_ptr TimerScheduler::Instance( uno::Reference const& xContext) { std::scoped_lock aGuard (maInstanceMutex); if (mpInstance == nullptr) { if (!xContext.is()) return nullptr; mpInstance.reset(new TimerScheduler(xContext)); mpInstance->create(); } return mpInstance; } TimerScheduler::TimerScheduler( uno::Reference const& xContext) { uno::Reference const xDesktop( frame::Desktop::create(xContext)); uno::Reference const xListener( new TerminateListener); // assuming the desktop can take ownership xDesktop->addTerminateListener(xListener); } SharedTimerTask TimerScheduler::CreateTimerTask ( const PresenterTimer::Task& rTask, const TimeValue& rDueTime, const sal_Int64 nRepeatInterval) { return std::make_shared(rTask, rDueTime, nRepeatInterval, ++mnTaskId); } void TimerScheduler::ScheduleTask (const SharedTimerTask& rpTask) { if (!rpTask) return; if (rpTask->mbIsCanceled) return; { std::scoped_lock aTaskGuard (maTaskContainerMutex); maScheduledTasks.insert(rpTask); } } void TimerScheduler::CancelTask (const sal_Int32 nTaskId) { // Set of scheduled tasks is sorted after their due times, not their // task ids. Therefore we have to do a linear search for the task to // cancel. { std::scoped_lock aGuard (maTaskContainerMutex); auto iTask = std::find_if(maScheduledTasks.begin(), maScheduledTasks.end(), [nTaskId](const SharedTimerTask& rxTask) { return rxTask->mnTaskId == nTaskId; }); if (iTask != maScheduledTasks.end()) maScheduledTasks.erase(iTask); } // The task that is to be canceled may be currently about to be // processed. Mark it with a flag that a) prevents a repeating task // from being scheduled again and b) tries to prevent its execution. { std::scoped_lock aGuard (maCurrentTaskMutex); if (mpCurrentTask && mpCurrentTask->mnTaskId == nTaskId) mpCurrentTask->mbIsCanceled = true; } // Let the main-loop cleanup in its own time } void TimerScheduler::NotifyTermination() { std::shared_ptr const pInstance(TimerScheduler::mpInstance); if (!pInstance) { return; } { std::scoped_lock aGuard(pInstance->maTaskContainerMutex); pInstance->maScheduledTasks.clear(); } { std::scoped_lock aGuard(pInstance->maCurrentTaskMutex); if (pInstance->mpCurrentTask) { pInstance->mpCurrentTask->mbIsCanceled = true; } } pInstance->m_Shutdown.set(); // rhbz#1425304 join thread before shutdown pInstance->join(); } void SAL_CALL TimerScheduler::run() { osl_setThreadName("sdext::presenter::TimerScheduler"); while (true) { // Get the current time. TimeValue aCurrentTime; if ( ! GetCurrentTime(aCurrentTime)) { // We can not get the current time and thus can not schedule anything. break; } // Restrict access to the maScheduledTasks member to one, mutex // guarded, block. SharedTimerTask pTask; sal_Int64 nDifference = 0; { std::scoped_lock aGuard (maTaskContainerMutex); // There are no more scheduled task. Leave this loop, function and // live of the TimerScheduler. if (maScheduledTasks.empty()) break; nDifference = GetTimeDifference( (*maScheduledTasks.begin())->maDueTime, aCurrentTime); if (nDifference <= 0) { pTask = *maScheduledTasks.begin(); maScheduledTasks.erase(maScheduledTasks.begin()); } } // Acquire a reference to the current task. { std::scoped_lock aGuard (maCurrentTaskMutex); mpCurrentTask = pTask; } if (!pTask) { // Wait until the first task becomes due. TimeValue aTimeValue; ConvertToTimeValue(aTimeValue, nDifference); // wait on condition variable, so the thread can be stopped m_Shutdown.wait(&aTimeValue); } else { // Execute task. if (pTask->maTask && !pTask->mbIsCanceled) { pTask->maTask(aCurrentTime); // Re-schedule repeating tasks. if (pTask->mnRepeatInterval > 0) { ConvertToTimeValue( pTask->maDueTime, ConvertFromTimeValue(pTask->maDueTime) + pTask->mnRepeatInterval); ScheduleTask(pTask); } } } // Release reference to the current task. { std::scoped_lock aGuard (maCurrentTaskMutex); mpCurrentTask.reset(); } } // While holding maInstanceMutex std::scoped_lock aInstance( maInstanceMutex ); mpLateDestroy = mpInstance; mpInstance.reset(); } bool TimerScheduler::GetCurrentTime (TimeValue& rCurrentTime) { TimeValue aSystemTime; if (osl_getSystemTime(&aSystemTime)) return osl_getLocalTimeFromSystemTime(&aSystemTime, &rCurrentTime); return false; } sal_Int64 TimerScheduler::GetTimeDifference ( const TimeValue& rTargetTime, const TimeValue& rCurrentTime) { return ConvertFromTimeValue(rTargetTime) - ConvertFromTimeValue(rCurrentTime); } void TimerScheduler::ConvertToTimeValue ( TimeValue& rTimeValue, const sal_Int64 nTimeDifference) { rTimeValue.Seconds = sal::static_int_cast(nTimeDifference / 1000000000L); rTimeValue.Nanosec = sal::static_int_cast(nTimeDifference % 1000000000L); } sal_Int64 TimerScheduler::ConvertFromTimeValue ( const TimeValue& rTimeValue) { return sal_Int64(rTimeValue.Seconds) * 1000000000L + rTimeValue.Nanosec; } //===== TimerTask ============================================================= namespace { TimerTask::TimerTask ( const PresenterTimer::Task& rTask, const TimeValue& rDueTime, const sal_Int64 nRepeatInterval, const sal_Int32 nTaskId) : maTask(rTask), maDueTime(rDueTime), mnRepeatInterval(nRepeatInterval), mnTaskId(nTaskId), mbIsCanceled(false) { } } // end of anonymous namespace //===== PresenterTimer ======================================================== ::rtl::Reference PresenterClockTimer::mpInstance; ::rtl::Reference PresenterClockTimer::Instance ( const css::uno::Reference& rxContext) { ::osl::MutexGuard aSolarGuard (::osl::Mutex::getGlobalMutex()); ::rtl::Reference pTimer; if (mpInstance.is()) { pTimer = mpInstance; } if ( ! pTimer.is()) { pTimer.set(new PresenterClockTimer(rxContext)); mpInstance = pTimer; } return pTimer; } PresenterClockTimer::PresenterClockTimer (const Reference& rxContext) : PresenterClockTimerInterfaceBase(m_aMutex), maDateTime(), mnTimerTaskId(PresenterTimer::NotAValidTaskId), mbIsCallbackPending(false), m_xContext(rxContext) { assert(m_xContext.is()); Reference xFactory = rxContext->getServiceManager(); if (xFactory.is()) mxRequestCallback.set( xFactory->createInstanceWithContext( "com.sun.star.awt.AsyncCallback", rxContext), UNO_QUERY_THROW); } PresenterClockTimer::~PresenterClockTimer() { if (mnTimerTaskId != PresenterTimer::NotAValidTaskId) { PresenterTimer::CancelTask(mnTimerTaskId); mnTimerTaskId = PresenterTimer::NotAValidTaskId; } Reference xComponent (mxRequestCallback, UNO_QUERY); if (xComponent.is()) xComponent->dispose(); mxRequestCallback = nullptr; } void PresenterClockTimer::AddListener (const SharedListener& rListener) { osl::MutexGuard aGuard (maMutex); maListeners.push_back(rListener); // Create a timer task when the first listener is added. if (mnTimerTaskId==PresenterTimer::NotAValidTaskId) { mnTimerTaskId = PresenterTimer::ScheduleRepeatedTask( m_xContext, [this] (TimeValue const& rTime) { return this->CheckCurrentTime(rTime); }, 0, 250000000 /*ns*/); } } void PresenterClockTimer::RemoveListener (const SharedListener& rListener) { osl::MutexGuard aGuard (maMutex); ListenerContainer::iterator iListener (::std::find( maListeners.begin(), maListeners.end(), rListener)); if (iListener != maListeners.end()) maListeners.erase(iListener); if (maListeners.empty()) { // We have no more clients and therefore are not interested in time changes. if (mnTimerTaskId != PresenterTimer::NotAValidTaskId) { PresenterTimer::CancelTask(mnTimerTaskId); mnTimerTaskId = PresenterTimer::NotAValidTaskId; } mpInstance = nullptr; } } oslDateTime PresenterClockTimer::GetCurrentTime() { TimeValue aCurrentTime; TimerScheduler::GetCurrentTime(aCurrentTime); oslDateTime aDateTime; osl_getDateTimeFromTimeValue(&aCurrentTime, &aDateTime); return aDateTime; } void PresenterClockTimer::CheckCurrentTime (const TimeValue& rCurrentTime) { css::uno::Reference xRequestCallback; css::uno::Reference xCallback; { osl::MutexGuard aGuard (maMutex); TimeValue aCurrentTime (rCurrentTime); oslDateTime aDateTime; if (osl_getDateTimeFromTimeValue(&aCurrentTime, &aDateTime)) { if (aDateTime.Seconds != maDateTime.Seconds || aDateTime.Minutes != maDateTime.Minutes || aDateTime.Hours != maDateTime.Hours) { // The displayed part of the current time has changed. // Prepare to call the listeners. maDateTime = aDateTime; // Schedule notification of listeners. if (mxRequestCallback.is() && ! mbIsCallbackPending) { mbIsCallbackPending = true; xRequestCallback = mxRequestCallback; xCallback = this; } } } } if (xRequestCallback.is() && xCallback.is()) xRequestCallback->addCallback(xCallback, Any()); } //----- XCallback ------------------------------------------------------------- void SAL_CALL PresenterClockTimer::notify (const css::uno::Any&) { ListenerContainer aListenerCopy; { osl::MutexGuard aGuard (maMutex); mbIsCallbackPending = false; aListenerCopy = maListeners; } for (const auto& rxListener : aListenerCopy) { rxListener->TimeHasChanged(maDateTime); } } } // end of namespace ::sdext::presenter /* vim:set shiftwidth=4 softtabstop=4 expandtab: */