Skip to content

Commit

Permalink
libcore: Various API improvements to core classes
Browse files Browse the repository at this point in the history
  • Loading branch information
skyjake committed Oct 23, 2017
1 parent f0b87e2 commit 075471b
Show file tree
Hide file tree
Showing 16 changed files with 375 additions and 52 deletions.
67 changes: 58 additions & 9 deletions doomsday/sdk/libcore/include/de/concurrency/async.h
Expand Up @@ -24,6 +24,7 @@
#include "../String"

#include <QThread>
#include <atomic>
#include <utility>

namespace de {
Expand All @@ -32,6 +33,7 @@ struct DENG2_PUBLIC AsyncTask : public QThread
{
virtual ~AsyncTask() {}
virtual void abort() = 0;
virtual void invalidate() = 0;
};

namespace internal {
Expand All @@ -40,8 +42,9 @@ template <typename Task, typename Completion>
class AsyncTaskThread : public AsyncTask
{
Task task;
Completion completion;
decltype(task()) result {}; // can't be void
Completion completion;
bool valid;

void run() override
{
Expand All @@ -58,15 +61,26 @@ class AsyncTaskThread : public AsyncTask
{
Loop::mainCall([this] ()
{
completion(result);
if (valid) completion(result);
deleteLater();
});
}

void invalidate() override
{
valid = false;
}

public:
AsyncTaskThread(Task const &task, Completion const &completion)
: task(task)
, completion(completion)
, valid(true)
{}

AsyncTaskThread(Task const &task)
: task(task)
, valid(false)
{}

void abort() override
Expand All @@ -79,20 +93,30 @@ class AsyncTaskThread : public AsyncTask
} // namespace internal

/**
* Executes an asynchronous callback in a background thread. After the background thread
* finishes, the result from the callback is passed to another callback that is called
* in the main thread.
* Executes an asynchronous callback in a background thread.
*
* After the background thread finishes, the result from the callback is passed to
* another callback that is called in the main thread.
*
* Must be called from the main thread.
*
* If it is possible that the completion becomes invalid (e.g., the object that
* started the operation is destroyed), you should use AsyncScope to automatically
* invalidate the completion callbacks of the started tasks.
*
* @param task Task callback. If an exception is thrown here, it will be
* quietly caught, and the completion callback will be called with
* a default-constructed result value.
* @param completion Completion callback. Takes one argument matching the type of
* the return value from @a task.
* a default-constructed result value. Note that if you return a
* pointer to an object and intend to pass ownership to the
* completion callback, the object will leak if the completion has
* been invalidated. Therefore, you should always pass ownership via
* std::shared_ptr or other reference-counted type.
*
* @param completion Completion callback to be called in the main thread. Takes one
* argument matching the type of the return value from @a task.
*
* @return Background thread object. The thread will delete itself after the completion
* callback has been called.
* callback has been called. You can pass this to AsyncScope for keeping track of.
*/
template <typename Task, typename Completion>
AsyncTask *async(Task const &task, Completion const &completion)
Expand All @@ -104,6 +128,31 @@ AsyncTask *async(Task const &task, Completion const &completion)
return t;
}

/*template <typename Task>
AsyncTask *async(Task const &task)
{
auto *t = new internal::AsyncTaskThread<Task, void *>(task);
t->start();
// Note: The thread will delete itself when finished.
return t;
}*/

/**
* Utility for invalidating the completion callbacks of async tasks whose initiator
* has gone out of scope.
*/
class DENG2_PUBLIC AsyncScope
{
public:
AsyncScope() = default;
~AsyncScope();

AsyncScope &operator += (AsyncTask *task);

private:
QSet<AsyncTask *> _tasks;
};

} // namespace de

#endif // LIBDENG2_ASYNCTASK_H
5 changes: 5 additions & 0 deletions doomsday/sdk/libcore/include/de/core/version.h
Expand Up @@ -122,6 +122,11 @@ class DENG2_PUBLIC Version

bool operator > (Version const &other) const;

/**
* Returns a user agent string for network requests.
*/
String userAgent() const;

/**
* Determines the operating system.
*/
Expand Down
31 changes: 31 additions & 0 deletions doomsday/sdk/libcore/include/de/data/gzip.h
@@ -0,0 +1,31 @@
/** @file gzip.h Process gzip data with zlib.
*
* @authors Copyright (c) 2017 Jaakko Keränen <jaakko.keranen@iki.fi>
*
* @par License
* LGPL: http://www.gnu.org/licenses/lgpl.html
*
* <small>This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or (at your
* option) any later version. 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 Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
*/

#ifndef DENG2_GZIP_H
#define DENG2_GZIP_H

#include "../libcore.h"
#include "../Block"

namespace de {

DENG2_PUBLIC Block gDecompress(Block const &gzData);

} // namespace de

#endif // DENG2_GZIP_H
2 changes: 2 additions & 0 deletions doomsday/sdk/libcore/include/de/data/pathtree.h
Expand Up @@ -235,6 +235,8 @@ class DENG2_PUBLIC PathTree : public Lockable
*/
Path path(QChar sep = '/') const;

DENG2_CAST_METHODS()

friend class PathTree;
friend struct PathTree::Impl;

Expand Down
8 changes: 8 additions & 0 deletions doomsday/sdk/libcore/include/de/data/record.h
Expand Up @@ -409,6 +409,12 @@ class DENG2_PUBLIC Record
/// @copydoc set()
Variable &set(String const &name, unsigned long value);

/// @copydoc set()
Variable &set(String const &name, Time const &value);

/// @copydoc set()
Variable &set(String const &name, Block const &value);

/**
* Sets the value of a variable, creating the variable if it doesn't exist.
*
Expand All @@ -417,6 +423,8 @@ class DENG2_PUBLIC Record
*/
Variable &set(String const &name, ArrayValue *value);

Variable &set(String const &name, Value *value);

/**
* Appends a word to the value of the variable.
*
Expand Down
3 changes: 3 additions & 0 deletions doomsday/sdk/libcore/include/de/data/recordvalue.h
Expand Up @@ -129,6 +129,9 @@ class DENG2_PUBLIC RecordValue

RecordValue *duplicateUnowned() const;

static RecordValue *takeRecord(Record *record);
static RecordValue *takeRecord(Record &&record);

public:
DENG2_PRIVATE(d)
};
Expand Down
3 changes: 3 additions & 0 deletions doomsday/sdk/libcore/include/de/data/string.h
Expand Up @@ -186,6 +186,9 @@ class DENG2_PUBLIC String : public QString
/// @return Copy of the string without whitespace.
String rightStrip() const;

/// Replaces all sequences of whitespace with single space characters.
String normalizeWhitespace() const;

/// Returns a lower-case version of the string.
String lower() const;

Expand Down
3 changes: 2 additions & 1 deletion doomsday/sdk/libcore/include/de/data/time.h
Expand Up @@ -156,6 +156,7 @@ class DENG2_PUBLIC Time : public ISerializable
ISODateOnly,
CompilerDateTime, // Oct 7 2013 03:18:36 (__DATE__ __TIME__)
HumanDate, ///< human-entered date (only with Time::fromText)
UnixLsStyleDateTime,
};

public:
Expand All @@ -165,7 +166,7 @@ class DENG2_PUBLIC Time : public ISerializable
Time();

Time(Time const &other);

Time(Time &&moved);

Time(QDateTime const &t);
Expand Down
44 changes: 44 additions & 0 deletions doomsday/sdk/libcore/src/concurrency/async.cpp
@@ -0,0 +1,44 @@
/** @file async.cpp Asynchronous utilities.
*
* @authors Copyright (c) 2017 Jaakko Keränen <jaakko.keranen@iki.fi>
*
* @par License
* LGPL: http://www.gnu.org/licenses/lgpl.html
*
* <small>This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or (at your
* option) any later version. 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 Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
*/

#include "de/Async"

namespace de {

AsyncScope::~AsyncScope()
{
foreach (AsyncTask *task, _tasks)
{
task->invalidate();
}
}

AsyncScope &AsyncScope::operator += (AsyncTask *task)
{
if (task)
{
_tasks.insert(task);
QObject::connect(task, &QObject::destroyed, [this] (QObject *obj)
{
_tasks.remove(static_cast<AsyncTask *>(obj));
});
}
return *this;
}

} // namespace de
30 changes: 15 additions & 15 deletions doomsday/sdk/libcore/src/data/bank.cpp
Expand Up @@ -147,8 +147,8 @@ DENG2_PIMPL(Bank)

if (data.get())
{
LOG_RES_VERBOSE("Item \"%s\" data cleared from memory (%i bytes)")
<< path(bank->d->sepChar) << data->sizeInMemory();
LOG_XVERBOSE("Item \"%s\" data cleared from memory (%i bytes)",
path(bank->d->sepChar) << data->sizeInMemory());
data->aboutToUnload();
data.reset();
}
Expand Down Expand Up @@ -195,8 +195,8 @@ DENG2_PIMPL(Bank)
// us. This may take an unspecified amount of time.
QScopedPointer<IData> loaded(bank->loadFromSource(*source));

LOG_RES_XVERBOSE("Loaded \"%s\" from source in %.2f seconds",
path(bank->d->sepChar) << startedAt.since());
LOG_XVERBOSE("Loaded \"%s\" from source in %.2f seconds",
path(bank->d->sepChar) << startedAt.since());

if (loaded.data())
{
Expand Down Expand Up @@ -228,15 +228,15 @@ DENG2_PIMPL(Bank)
std::unique_ptr<IData> blank(bank->newData());
reader >> *blank->asSerializable();
setData(blank.release());
LOG_RES_XVERBOSE("Deserialized \"%s\" in %.2f seconds",
path(bank->d->sepChar) << startedAt.since());
LOG_XVERBOSE("Deserialized \"%s\" in %.2f seconds",
path(bank->d->sepChar) << startedAt.since());
return; // Done!
}
// We cannot use this.
}
catch (Error const &er)
{
LOG_RES_WARNING("Failed to deserialize \"%s\":\n")
LOG_WARNING("Failed to deserialize \"%s\":\n")
<< path(bank->d->sepChar) << er.asText();
}

Expand Down Expand Up @@ -284,7 +284,7 @@ DENG2_PIMPL(Bank)
// to check later whether the data is still fresh.
serial.reset(&containingFolder.createFile(name(), Folder::ReplaceExisting));

LOG_RES_XVERBOSE("Serializing into %s", serial->description());
LOG_XVERBOSE("Serializing into %s", serial->description());

Writer(*serial).withHeader()
<< source->modifiedAt()
Expand Down Expand Up @@ -474,7 +474,7 @@ DENG2_PIMPL(Bank)
}
catch (Error const &er)
{
LOG_RES_WARNING("Failed to load \"%s\" from source:\n") << _path << er.asText();
LOG_WARNING("Failed to load \"%s\" from source:\n") << _path << er.asText();
}
// Ensure a blocking load completes.
item().post();
Expand All @@ -486,12 +486,12 @@ DENG2_PIMPL(Bank)
{
DENG2_ASSERT(_bank.d->serialCache != 0);

LOG_RES_XVERBOSE("Serializing \"%s\"", _path);
LOG_XVERBOSE("Serializing \"%s\"", _path);
item().changeCache(*_bank.d->serialCache);
}
catch (Error const &er)
{
LOG_RES_WARNING("Failed to serialize \"%s\" to hot storage:\n")
LOG_WARNING("Failed to serialize \"%s\" to hot storage:\n")
<< _path << er.asText();
}
}
Expand All @@ -505,7 +505,7 @@ DENG2_PIMPL(Bank)
}
catch (Error const &er)
{
LOG_RES_WARNING("Error when unloading \"%s\":\n")
LOG_WARNING("Error when unloading \"%s\":\n")
<< _path << er.asText();
}
}
Expand Down Expand Up @@ -911,7 +911,7 @@ Bank::IData &Bank::data(DotPath const &path) const
item.reset();
item.unlock();

LOG_RES_XVERBOSE("Loading \"%s\"...", path);
LOG_XVERBOSE("Loading \"%s\"...", path);

Time requestedAt;
d->load(path, BeforeQueued);
Expand All @@ -927,11 +927,11 @@ Bank::IData &Bank::data(DotPath const &path) const

if (waitTime > 0.0)
{
LOG_RES_VERBOSE("\"%s\" loaded (waited %.3f seconds)") << path << waitTime;
LOG_VERBOSE("\"%s\" loaded (waited %.3f seconds)") << path << waitTime;
}
else
{
LOG_RES_VERBOSE("\"%s\" loaded") << path;
LOG_VERBOSE("\"%s\" loaded") << path;
}

return *item.data;
Expand Down

0 comments on commit 075471b

Please sign in to comment.