summaryrefslogtreecommitdiff
path: root/include/mbgl/actor/actor.hpp
blob: 00e1bb82f8200968609500f901ddcd85a70ce38d (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
#pragma once

#include <mbgl/actor/mailbox.hpp>
#include <mbgl/actor/message.hpp>
#include <mbgl/actor/actor_ref.hpp>
#include <mbgl/util/noncopyable.hpp>

#include <memory>
#include <future>

namespace mbgl {

/*
    An `Actor<O>` is an owning reference to an asynchronous object of type `O`: an "actor".
    Communication with an actor happens via message passing: you send a message to the object
    (using `invoke`), passing a pointer to the member function to call and arguments which
    are then forwarded to the actor.

    The actor receives messages sent to it asynchronously, in a manner defined its `Scheduler`.
    To store incoming messages before their receipt, each actor has a `Mailbox`, which acts as
    a FIFO queue. Messages sent from actor S to actor R are guaranteed to be processed in the
    order sent. However, relative order of messages sent by two *different* actors S1 and S2
    to R is *not* guaranteed (and can't be: S1 and S2 may be acting asynchronously with respect
    to each other).

    An `Actor<O>` can be converted to an `ActorRef<O>`, a non-owning value object representing
    a (weak) reference to the actor. Messages can be sent via the `Ref` as well.

    It's safe -- and encouraged -- to pass `Ref`s between actors via messages. This is how two-way
    communication and other forms of collaboration between multiple actors is accomplished.

    It's safe for a `Ref` to outlive its `Actor` -- the reference is "weak", and does not extend
    the lifetime of the owning Actor, and sending a message to a `Ref` whose `Actor` has died is
    a no-op. (In the future, a dead-letters queue or log may be implemented.)

    Construction and destruction of an actor is currently synchronous: the corresponding `O`
    object is constructed synchronously by the `Actor` constructor, and destructed synchronously
    by the `~Actor` destructor, after ensuring that the `O` is not currently receiving an
    asynchronous message. (Construction and destruction may change to be asynchronous in the
    future.) The constructor of `O` is passed an `ActorRef<O>` referring to itself (which it
    can use to self-send messages), followed by the forwarded arguments passed to `Actor<O>`.

    Please don't send messages that contain shared pointers or references. That subverts the
    purpose of the actor model: prohibiting direct concurrent access to shared state.
*/

template <class Object>
class Actor : public util::noncopyable {
public:
    template <class... Args>
    Actor(Scheduler& scheduler, Args&&... args_)
        : mailbox(std::make_shared<Mailbox>(scheduler)),
          object(self(), std::forward<Args>(args_)...) {
    }

    ~Actor() {
        mailbox->close();
    }

    template <typename Fn, class... Args>
    void invoke(Fn fn, Args&&... args) {
        mailbox->push(actor::makeMessage(object, fn, std::forward<Args>(args)...));
    }

    template <typename Fn, class... Args>
    auto ask(Fn fn, Args&&... args) {
        // Result type is deduced from the function's return type
        using ResultType = typename std::result_of<decltype(fn)(Object, Args...)>::type;

        std::promise<ResultType> promise;
        auto future = promise.get_future();
        mailbox->push(actor::makeMessage(std::move(promise), object, fn, std::forward<Args>(args)...));
        return future;
    }

    ActorRef<std::decay_t<Object>> self() {
        return ActorRef<std::decay_t<Object>>(object, mailbox);
    }

    operator ActorRef<std::decay_t<Object>>() {
        return self();
    }

private:
    std::shared_ptr<Mailbox> mailbox;
    Object object;
};

} // namespace mbgl