Commit bf0d17db authored by Nicolas Carion's avatar Nicolas Carion

[Timeline2][Model] Add mutexes to the model

parent 9a778f5e
/***************************************************************************
* Copyright (C) 2017 by Nicolas Carion *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* 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) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* 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, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#ifndef MACROS_H
#define MACROS_H
//This convenience macro adds lock/unlock ability to a given lambda function
#define LOCK_IN_LAMBDA(lambda) \
lambda = [this, lambda]() { \
m_lock.lockForWrite(); \
bool res_lambda = lambda(); \
m_lock.unlock(); \
return res_lambda; \
};
/*This convenience macro locks the mutex for reading.
Note that it might happen that a thread is executing a write operation that requires
reading a Read-protected property. In that case, we try to write lock it first (this will be granted since the lock is recursive)
*/
#define READ_LOCK() \
std::unique_ptr<QReadLocker> rlocker(new QReadLocker(nullptr)); \
std::unique_ptr<QWriteLocker> wlocker(new QWriteLocker(nullptr)); \
if (m_lock.tryLockForWrite()) { \
/*we yield ownership of the lock to the WriteLocker*/ \
m_lock.unlock(); \
wlocker.reset(new QWriteLocker(&m_lock)); \
} else { \
rlocker.reset(new QReadLocker(&m_lock)); \
}
#define PUSH_UNDO(undo, redo, text) \
LOCK_IN_LAMBDA(undo) \
LOCK_IN_LAMBDA(redo) \
if (auto ptr = m_undoStack.lock()) { \
ptr->push(new FunctionalUndoCommand(undo, redo, text)); \
} else { \
qDebug() << "ERROR : unable to access undo stack"; \
Q_ASSERT(false); \
}
#endif
......@@ -29,6 +29,7 @@
#include <mlt++/MltTractor.h>
#include <mlt++/MltProfile.h>
#include <QDebug>
#include "macros.hpp"
TimelineItemModel::TimelineItemModel(Mlt::Profile *profile, std::weak_ptr<DocUndoStack> undo_stack) :
QAbstractItemModel()
......@@ -70,6 +71,7 @@ TimelineItemModel::~TimelineItemModel()
QModelIndex TimelineItemModel::index(int row, int column, const QModelIndex &parent) const
{
READ_LOCK();
if (column > 0)
return QModelIndex();
// qDebug() << "TimelineItemModel::index" << row << column << parent;
......@@ -117,6 +119,7 @@ QModelIndex TimelineItemModel::makeTrackIndexFromID(int tid) const
QModelIndex TimelineItemModel::parent(const QModelIndex &index) const
{
READ_LOCK();
// qDebug() << "TimelineItemModel::parent"<< index;
if (index == QModelIndex()) {
return index;
......@@ -134,6 +137,7 @@ QModelIndex TimelineItemModel::parent(const QModelIndex &index) const
int TimelineItemModel::rowCount(const QModelIndex &parent) const
{
READ_LOCK();
if (parent.isValid()) {
const int id = (int)parent.internalId();
if (isClip(id) || !isTrack(id)) {
......@@ -185,6 +189,7 @@ QHash<int, QByteArray> TimelineItemModel::roleNames() const
QVariant TimelineItemModel::data(const QModelIndex &index, int role) const
{
READ_LOCK();
// qDebug() << "DATA requested "<<index<<roleNames()[role];
if (!m_tractor || !index.isValid()) {
// qDebug() << "DATA abort. Index validity="<<index.isValid();
......
......@@ -35,15 +35,10 @@
#include <mlt++/MltProfile.h>
#include <queue>
#include "macros.hpp"
int TimelineModel::next_id = 0;
#define PUSH_UNDO(undo, redo, text) \
if (auto ptr = m_undoStack.lock()) { \
ptr->push(new FunctionalUndoCommand(undo, redo, text)); \
} else { \
qDebug() << "ERROR : unable to access undo stack"; \
Q_ASSERT(false); \
}
TimelineModel::TimelineModel(Mlt::Profile *profile, std::weak_ptr<DocUndoStack> undo_stack) :
......@@ -51,7 +46,8 @@ TimelineModel::TimelineModel(Mlt::Profile *profile, std::weak_ptr<DocUndoStack>
m_snaps(new SnapModel()),
m_undoStack(undo_stack),
m_profile(profile),
m_blackClip(new Mlt::Producer(*profile,"color:black"))
m_blackClip(new Mlt::Producer(*profile,"color:black")),
m_lock(QReadWriteLock::Recursive)
{
// Create black background track
m_blackClip->set("id", "black_track");
......@@ -76,6 +72,7 @@ TimelineModel::~TimelineModel()
int TimelineModel::getTracksCount() const
{
READ_LOCK();
int count = m_tractor->count();
Q_ASSERT(count >= 0);
// don't count the black background track
......@@ -85,41 +82,53 @@ int TimelineModel::getTracksCount() const
int TimelineModel::getClipsCount() const
{
return static_cast<int>(m_allClips.size());
READ_LOCK();
int size = int(m_allClips.size());
return size;
}
int TimelineModel::getClipTrackId(int cid) const
{
READ_LOCK();
Q_ASSERT(m_allClips.count(cid) > 0);
const auto clip = m_allClips.at(cid);
return clip->getCurrentTrackId();
int tid = clip->getCurrentTrackId();
return tid;
}
int TimelineModel::getClipPosition(int cid) const
{
READ_LOCK();
Q_ASSERT(m_allClips.count(cid) > 0);
const auto clip = m_allClips.at(cid);
return clip->getPosition();
int pos = clip->getPosition();
return pos;
}
int TimelineModel::getClipPlaytime(int cid) const
{
READ_LOCK();
Q_ASSERT(m_allClips.count(cid) > 0);
const auto clip = m_allClips.at(cid);
return clip->getPlaytime();
int playtime = clip->getPlaytime();
return playtime;
}
int TimelineModel::getTrackClipsCount(int tid) const
{
return getTrackById_const(tid)->getClipsCount();
READ_LOCK();
int count = getTrackById_const(tid)->getClipsCount();
return count;
}
int TimelineModel::getTrackPosition(int tid) const
{
READ_LOCK();
Q_ASSERT(m_iteratorTable.count(tid) > 0);
auto it = m_allTracks.begin();
return (int)std::distance(it, (decltype(it))m_iteratorTable.at(tid));
int pos = (int)std::distance(it, (decltype(it))m_iteratorTable.at(tid));
return pos;
}
bool TimelineModel::requestClipMove(int cid, int tid, int position, bool updateView, Fun &undo, Fun &redo)
......@@ -149,6 +158,7 @@ bool TimelineModel::requestClipMove(int cid, int tid, int position, bool updateV
bool TimelineModel::requestClipMove(int cid, int tid, int position, bool updateView, bool logUndo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_allClips.count(cid) > 0);
if (m_allClips[cid]->getPosition() == position && getClipTrackId(cid) == tid) {
return true;
......@@ -174,6 +184,7 @@ bool TimelineModel::requestClipMove(int cid, int tid, int position, bool update
int TimelineModel::suggestClipMove(int cid, int tid, int position)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(isClip(cid));
Q_ASSERT(isTrack(tid));
int currentPos = getClipPosition(cid);
......@@ -210,6 +221,7 @@ int TimelineModel::suggestClipMove(int cid, int tid, int position)
bool TimelineModel::requestClipInsertion(std::shared_ptr<Mlt::Producer> prod, int trackId, int position, int &id)
{
QWriteLocker locker(&m_lock);
Fun undo = [](){return true;};
Fun redo = [](){return true;};
bool result = requestClipInsertion(prod, trackId, position, id, undo, redo);
......@@ -243,6 +255,7 @@ bool TimelineModel::requestClipInsertion(std::shared_ptr<Mlt::Producer> prod, in
bool TimelineModel::requestClipDeletion(int cid)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(isClip(cid));
if (m_groups->isInGroup(cid)) {
return requestGroupDeletion(cid);
......@@ -283,6 +296,7 @@ bool TimelineModel::requestClipDeletion(int cid, Fun& undo, Fun& redo)
bool TimelineModel::requestGroupMove(int cid, int gid, int delta_track, int delta_pos, bool updateView, bool logUndo)
{
QWriteLocker locker(&m_lock);
std::function<bool (void)> undo = [](){return true;};
std::function<bool (void)> redo = [](){return true;};
Q_ASSERT(m_allGroups.count(gid) > 0);
......@@ -331,6 +345,7 @@ bool TimelineModel::requestGroupMove(int cid, int gid, int delta_track, int delt
bool TimelineModel::requestGroupDeletion(int cid)
{
QWriteLocker locker(&m_lock);
Fun undo = [](){return true;};
Fun redo = [](){return true;};
// we do a breadth first exploration of the group tree, ungroup (delete) every inner node, and then delete all the leaves.
......@@ -375,6 +390,7 @@ bool TimelineModel::requestGroupDeletion(int cid)
bool TimelineModel::requestClipResize(int cid, int size, bool right, bool logUndo, bool snapping)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(isClip(cid));
if (snapping) {
Fun temp_undo = [](){return true;};
......@@ -438,6 +454,7 @@ bool TimelineModel::requestClipTrim(int cid, int delta, bool right, bool ripple,
bool TimelineModel::requestClipsGroup(const std::unordered_set<int>& ids)
{
QWriteLocker locker(&m_lock);
for (int id : ids) {
if (isClip(id)) {
if (getClipTrackId(id) == -1) {
......@@ -458,6 +475,7 @@ bool TimelineModel::requestClipsGroup(const std::unordered_set<int>& ids)
bool TimelineModel::requestClipUngroup(int id)
{
QWriteLocker locker(&m_lock);
Fun undo = [](){return true;};
Fun redo = [](){return true;};
bool result = requestClipUngroup(id, undo, redo);
......@@ -469,11 +487,13 @@ bool TimelineModel::requestClipUngroup(int id)
bool TimelineModel::requestClipUngroup(int id, Fun& undo, Fun& redo)
{
QWriteLocker locker(&m_lock);
return m_groups->ungroupItem(id, undo, redo);
}
bool TimelineModel::requestTrackInsertion(int position, int &id)
{
QWriteLocker locker(&m_lock);
Fun undo = [](){return true;};
Fun redo = [](){return true;};
bool result = requestTrackInsertion(position, id, undo, redo);
......@@ -507,6 +527,7 @@ bool TimelineModel::requestTrackInsertion(int position, int &id, Fun& undo, Fun&
bool TimelineModel::requestTrackDeletion(int tid)
{
QWriteLocker locker(&m_lock);
Fun undo = [](){return true;};
Fun redo = [](){return true;};
bool result = requestTrackDeletion(tid, undo, redo);
......
......@@ -28,6 +28,7 @@
#include <unordered_set>
#include <mlt++/MltTractor.h>
#include "undohelper.hpp"
#include <QReadWriteLock>
class TrackModel;
class ClipModel;
......@@ -348,6 +349,8 @@ protected:
// The black track producer. It's length / out should always be adjusted to the projects's length
std::unique_ptr<Mlt::Producer> m_blackClip;
mutable QReadWriteLock m_lock; //This is a lock that ensures safety in case of concurrent access
//what follows are some virtual function that corresponds to the QML. They are implemented in TimelineItemModel
protected:
virtual void _beginRemoveRows(const QModelIndex&, int , int) = 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment