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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! Workers are a way to offload tasks to web workers. These are run concurrently using
//! [web-workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers).
//!
//! # Types of Workers
//!
//! ## Reaches
//!
//! * Public - There will exist at most one instance of a Public Worker at any given time.
//!   Bridges will spawn or connect to an already spawned worker in a web worker.
//!   When no bridges are connected to this worker, the worker will disappear.
//!
//! * Private - Spawn a new worker in a web worker for every new bridge. This is good for
//!   moving shared but independent behavior that communicates with the browser out of components.
//!   When the the connected bridge is dropped, the worker will disappear.
//!
//! # Communicating with workers
//!
//! ## Bridges
//!
//! A bridge allows bi-directional communication between an worker and a component.
//! Bridges also allow workers to communicate with one another.
//!
//! ## Dispatchers
//!
//! A dispatcher allows uni-directional communication between a component and an worker.
//! A dispatcher allows a component to send messages to an worker.
//!
//! # Overhead
//!
//! Workers use web workers (i.e. Private and Public). They incur a serialization overhead on the
//! messages they send and receive. Workers use [bincode](https://!github.com/servo/bincode)
//! to communicate with other browser worker, so the cost is substantially higher
//! than just calling a function.

#![cfg_attr(docsrs, feature(doc_cfg))]

mod link;
mod pool;
mod worker;

pub use link::WorkerLink;
pub(crate) use link::*;
pub(crate) use pool::*;
pub use pool::{Dispatched, Dispatcher};
use std::cell::RefCell;
pub use worker::{Private, PrivateWorker, Public, PublicWorker};

use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;

/// Alias for `Rc<RefCell<T>>`
pub type Shared<T> = Rc<RefCell<T>>;

/// Alias for `Rc<dyn Fn(IN)>`
pub type Callback<IN> = Rc<dyn Fn(IN)>;

/// Declares the behavior of the worker.
pub trait Worker: Sized + 'static {
    /// Reach capability of the worker.
    type Reach: Discoverer<Worker = Self>;
    /// Type of an input message.
    type Message;
    /// Incoming message type.
    type Input;
    /// Outgoing message type.
    type Output;

    /// Creates an instance of an worker.
    fn create(link: WorkerLink<Self>) -> Self;

    /// This method called on every update message.
    fn update(&mut self, msg: Self::Message);

    /// This method called on when a new bridge created.
    fn connected(&mut self, _id: HandlerId) {}

    /// This method called on every incoming message.
    fn handle_input(&mut self, msg: Self::Input, id: HandlerId);

    /// This method called on when a new bridge destroyed.
    fn disconnected(&mut self, _id: HandlerId) {}

    /// This method called when the worker is destroyed.
    fn destroy(&mut self) {}

    /// Represents the name of loading resource for remote workers which
    /// have to live in a separate files.
    fn name_of_resource() -> &'static str {
        "main.js"
    }

    /// Indicates whether the name of the resource is relative.
    ///
    /// The default implementation returns `false`, which will cause the result
    /// returned by [`Self::name_of_resource`] to be interpreted as an absolute
    /// URL. If `true` is returned, it will be interpreted as a relative URL.
    fn resource_path_is_relative() -> bool {
        false
    }

    /// Signifies if resource is a module.
    /// This has pending browser support.
    fn is_module() -> bool {
        false
    }
}

/// Id of responses handler.
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone, Copy)]
pub struct HandlerId(usize, bool);

impl HandlerId {
    fn new(id: usize, respondable: bool) -> Self {
        HandlerId(id, respondable)
    }
    fn raw_id(self) -> usize {
        self.0
    }
    /// Indicates if a handler id corresponds to callback in the Worker runtime.
    pub fn is_respondable(self) -> bool {
        self.1
    }
}

/// Determine a visibility of an worker.
#[doc(hidden)]
pub trait Discoverer {
    type Worker: Worker;

    /// Spawns an worker and returns `Bridge` implementation.
    fn spawn_or_join(
        _callback: Option<Callback<<Self::Worker as Worker>::Output>>,
    ) -> Box<dyn Bridge<Self::Worker>>;
}

/// Bridge to a specific kind of worker.
pub trait Bridge<W: Worker> {
    /// Send a message to an worker.
    fn send(&mut self, msg: W::Input);
}

/// This trait allows registering or getting the address of a worker.
pub trait Bridged: Worker + Sized + 'static {
    /// Creates a messaging bridge between a worker and the component.
    fn bridge(callback: Callback<Self::Output>) -> Box<dyn Bridge<Self>>;
}

impl<T> Bridged for T
where
    T: Worker,
    <T as Worker>::Reach: Discoverer<Worker = T>,
{
    fn bridge(callback: Callback<Self::Output>) -> Box<dyn Bridge<Self>> {
        Self::Reach::spawn_or_join(Some(callback))
    }
}