summaryrefslogtreecommitdiffstats
path: root/src/util/funclog.h
blob: 7b81e4107f0bff03316e9bf54d0a5e5369d79a1f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// SPDX-License-Identifier: GPL-2.0-or-later
/** \file FuncLog
 * A log of functions that can be appended to and played back later.
 */
#ifndef INKSCAPE_UTIL_FUNCLOG_H
#define INKSCAPE_UTIL_FUNCLOG_H

#include <utility>
#include <exception>
#include "util/pool.h"

namespace Inkscape {
namespace Util {

/**
 * A FuncLog is effectively a std::vector<std::function<void()>>, with the ability to hold
 * move-only function types and enforced run-once semantics.
 *
 * The main difference is an efficient internal representation that stores the contents nearly
 * contiguously. This gives a 2x speedup when std::function uses the small-lambda optimisation,
 * and a 7x speedup when it has to heap-allocate.
 */
class FuncLog final
{
public:
    FuncLog() = default;
    FuncLog(FuncLog &&other) noexcept { movefrom(other); }
    FuncLog &operator=(FuncLog &&other) noexcept { destroy(); movefrom(other); return *this; }
    ~FuncLog() { destroy(); }

    /**
     * Append a callable object to the log.
     * On exception, no object is inserted, though memory will not be returned immediately.
     */
    template <typename F>
    void emplace(F &&f)
    {
        using Fd = typename std::decay<F>::type;
        auto entry = pool.allocate<Entry<Fd>>();
        new (entry) Entry<Fd>(std::forward<F>(f));
        *lastnext = entry;
        lastnext = &entry->next;
        entry->next = nullptr;
    }

    /**
     * Execute and destroy each callable in the log.
     * On exception, all remaining callables are destroyed.
     * \post empty() == true
     */
    void exec();

    /// Convenience alias for exec().
    void operator()() { exec(); }

    /**
     * Execute and destroy each callable in the log while condition \a c() is true, then destroy the rest.
     * On exception, all remaining callables are destroyed.
     * \post empty() == true
     */
    template <typename C>
    void exec_while(C &&c)
    {
        for (auto h = first; h; destroy_and_advance(h)) {
            try {
                if (!c()) {
                    destroy_from(h);
                    break;
                }
                (*h)();
            } catch (...) {
                destroy_from(h);
                reset();
                std::rethrow_exception(std::current_exception());
            }
        }
        reset();
    }

    /**
     * Destroy all callables in the log without executing them.
     * \post empty() == true
     */
    void clear() { destroy(); reset(); }

    bool empty() const { return !first; }

private:
    struct Header
    {
        Header *next;
        virtual ~Header() = default;
        virtual void operator()() = 0;
    };

    template <typename Fd>
    struct Entry : Header
    {
        Fd f;
        template <typename F>
        Entry(F &&f) : f(std::forward<F>(f)) {}
        void operator()() override { f(); }
    };

    Pool pool;
    Header *first = nullptr;
    Header **lastnext = &first;

    void destroy() { destroy_from(first); }
    static void destroy_from(Header *h) { while (h) destroy_and_advance(h); }
    static void destroy_and_advance(Header *&h) noexcept;
    void reset() noexcept;
    void movefrom(FuncLog &other) noexcept;
};

} // namespace Util
} // namespace Inkscape

#endif // INKSCAPE_UTIL_FUNCLOG_H