pharos-0.5.3/.cargo_vcs_info.json0000644000000001120000000000100123270ustar { "git": { "sha1": "f5b985a59cfeced5ae66325d477b6c982c9408bf" } } pharos-0.5.3/.github/PULL_REQUEST_TEMPLATE.md000064400000000000000000000003750072674642500163230ustar 00000000000000 pharos-0.5.3/.github/funding.yml000064400000000000000000000000250072674642500146670ustar 00000000000000liberapay: najamelan pharos-0.5.3/.github/workflows/ci.yml000064400000000000000000000030530072674642500156710ustar 00000000000000name: ci on : [push, pull_request] jobs: linux-stable: name: Linux Rust Stable runs-on: ubuntu-latest steps: - name: Install latest stable Rust uses: actions-rs/toolchain@v1 with: toolchain: stable override: true components: clippy - name: Checkout crate uses: actions/checkout@v2 - name: Run tests run: bash ci/test.bash linux-nightly: name: Linux Rust Nightly runs-on: ubuntu-latest steps: - name: Install latest nightly Rust uses: actions-rs/toolchain@v1 with: toolchain: nightly override: true components: clippy - name: Checkout crate uses: actions/checkout@v2 - name: Run clippy run : bash ci/clippy.bash - name: Build documentation run : bash ci/doc.bash - name: Run tests run : bash ci/test.bash - name: Install cargo-tarpaulin binary crate uses: actions-rs/install@v0.1 with: crate: cargo-tarpaulin version: latest use-tool-cache: true - name: Run cargo-tarpaulin run : | cargo tarpaulin --out Xml - name: Upload to codecov.io uses: codecov/codecov-action@v1.5.2 - name: install wasm-pack uses: jetli/wasm-pack-action@v0.3.0 with: # Optional version of wasm-pack to install(eg. 'v0.9.1', 'latest') version: 'latest' - name: Run tests on wasm run: bash ci/wasm.bash pharos-0.5.3/.gitignore000064400000000000000000000000360072674642500131440ustar 00000000000000/target **/*.rs.bk Cargo.lock pharos-0.5.3/CHANGELOG.md000064400000000000000000000061550072674642500127750ustar 00000000000000# Pharos Changelog ## [Unreleased] [Unreleased]: https://github.com/najamelan/pharos/compare/0.5.2...dev ## [0.5.2] - 2021-06-10 [0.5.2]: https://github.com/najamelan/pharos/compare/0.5.1...0.5.2 ### Fixed - Remove external_doc for rustdoc 1.54. Thanks to @rajivshah3 ## 0.5.1 - 2021-02-18 - Add `ObservableLocal` for observable types that are `!Send`. ## 0.5.0 - 2021-02-17 - **BREAKING CHANGE**: `Observable::observe` is now an async function. This was needed to make it possible to send events to a pharos object from different async tasks. So far notifying was async, but observing was not. However in order to be able to use a mutex, we need both operations to be one or the other. On wasm, one cannot block the thread hence the choice for an async mutex, but in order to lock that we have to be in async context. A new helper type `SharedPharos` has been introduced to conveniently use pharos from a shared reference. - **BREAKING CHANGE**: rename `pharos::Error` to `PharErr`. I want to move away from types that are just called `Error`. This allows conveniently exporting the error type at crate level. - no longer depend on futures-channel appart from the main futures lib. It's annoying if a dependant crate want's to patch futures in Cargo.toml. - move to github actions after travis becomes a paid service. ## 0.4.2 - 2019-11-13 - drop dependency on log. ## 0.4.1 - 2019-11-13 - update dependencies to futures 0.3.1. - CI testing on stable rust. ## 0.4.0 - 2019-09-28 - **BREAKING CHANGE**: The notify function had a sub optimal implemetation and did not allow notifying observers from within `poll_*` functions. It has been replaced with an implementation of Sink on Pharos. - got rid of dependency on pin_project. - as Error::kind returns a reference to the error kind, you can now compare `ErrorKind::SomeVariant == err.kind()` without having to write: `&ErrorKind::SomeVariant == err.kind()`. - updated to futures-preview 0.3.0-alpha.19 ## 0.3.2 - 2019-09-23 - check spelling ## 0.3.1 - 2019-09-23 - **BREAKING CHANGE**: Last minute change of heart. I removed two API methods whose only "merit" was to hide a Box::new from the user. - fix docs.rs showing readme ## 0.3.0 - 2019-09-23 - yanked **BREAKING CHANGE**: This is an almost complete rewrite with a much improved API, documentation, ... - Only have one Observable trait that takes options rather than UboundedObservable. - Allow filtering events with a predicate. - Many small improvements. Please have a look at the readme and the API docs for more. ## 0.2.2 - 2019-08-26 - update to futures 0.3.0-alpha.18 - remove async_await feature (from 1.39 this crate should compile on stable) ## 0.2.1 - 2019-08-02 - remove `await_macro` feature use and convert to new await syntax. - implement `Default` for `Pharos`. ## 0.2.0 - 2019-07-18 - **BREAKING CHANGE**: Update dependencies. The Futures library has some changes, which introduce a breaking change. Bounded channels are no longer queue_size + 1. They now are queue_size. This means that `Observable::observe( queue_size: usize )` will now yield a `futures::channel::Receiver` of the exact `queue_size`. pharos-0.5.3/Cargo.lock0000644000000473730000000000100103260ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "assert_matches" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-attributes" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ "quote", "syn", ] [[package]] name = "async-channel" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" dependencies = [ "concurrent-queue", "event-listener", "futures-core", ] [[package]] name = "async-executor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", "once_cell", "slab", ] [[package]] name = "async-global-executor" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" dependencies = [ "async-channel", "async-executor", "async-io", "async-mutex", "blocking", "futures-lite", "num_cpus", "once_cell", ] [[package]] name = "async-io" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" dependencies = [ "concurrent-queue", "futures-lite", "libc", "log", "once_cell", "parking", "polling", "slab", "socket2", "waker-fn", "winapi", ] [[package]] name = "async-lock" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" dependencies = [ "event-listener", ] [[package]] name = "async-mutex" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" dependencies = [ "event-listener", ] [[package]] name = "async-process" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b21b63ab5a0db0369deb913540af2892750e42d949faacc7a61495ac418a1692" dependencies = [ "async-io", "blocking", "cfg-if", "event-listener", "futures-lite", "libc", "once_cell", "signal-hook", "winapi", ] [[package]] name = "async-std" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952" dependencies = [ "async-attributes", "async-channel", "async-global-executor", "async-io", "async-lock", "async-process", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", "futures-lite", "gloo-timers", "kv-log-macro", "log", "memchr", "num_cpus", "once_cell", "pin-project-lite", "pin-utils", "slab", "wasm-bindgen-futures", ] [[package]] name = "async-task" version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" [[package]] name = "async_executors" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b13b311cd10e80105651ad640a6741991d147787badb4141e8e1b7fd59816f5" dependencies = [ "async-std", "blanket", "futures-core", "futures-task", "futures-util", "pin-project", "rustc_version", ] [[package]] name = "atomic-waker" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "blanket" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b04ce3d2372d05d1ef4ea3fdf427da6ae3c17ca06d688a107b5344836276bc3" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "blocking" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9" dependencies = [ "async-channel", "async-task", "atomic-waker", "fastrand", "futures-lite", "once_cell", ] [[package]] name = "bumpalo" version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" [[package]] name = "cache-padded" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" [[package]] name = "cc" version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "concurrent-queue" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" dependencies = [ "cache-padded", ] [[package]] name = "console_error_panic_hook" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ "cfg-if", "wasm-bindgen", ] [[package]] name = "crossbeam-utils" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" dependencies = [ "cfg-if", "lazy_static", ] [[package]] name = "ctor" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" dependencies = [ "quote", "syn", ] [[package]] name = "event-listener" version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" [[package]] name = "fastrand" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" dependencies = [ "instant", ] [[package]] name = "futures" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" [[package]] name = "futures-executor" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" [[package]] name = "futures-lite" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" dependencies = [ "fastrand", "futures-core", "futures-io", "memchr", "parking", "pin-project-lite", "waker-fn", ] [[package]] name = "futures-macro" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" dependencies = [ "autocfg", "proc-macro-hack", "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" [[package]] name = "futures-task" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" [[package]] name = "futures-util" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" dependencies = [ "autocfg", "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "proc-macro-hack", "proc-macro-nested", "slab", ] [[package]] name = "gloo-timers" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" dependencies = [ "futures-channel", "futures-core", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "js-sys" version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" dependencies = [ "wasm-bindgen", ] [[package]] name = "kv-log-macro" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ "log", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673" [[package]] name = "log" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if", "value-bag", ] [[package]] name = "memchr" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "num_cpus" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "once_cell" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "parking" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "pharos" version = "0.5.3" dependencies = [ "assert_matches", "async-std", "async_executors", "futures", "rustc_version", "wasm-bindgen-test", ] [[package]] name = "pin-project" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "pin-project-lite" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "polling" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92341d779fa34ea8437ef4d82d440d5e1ce3f3ff7f824aa64424cd481f9a1f25" dependencies = [ "cfg-if", "libc", "log", "wepoll-ffi", "winapi", ] [[package]] name = "proc-macro-hack" version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro-nested" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" dependencies = [ "unicode-xid", ] [[package]] name = "quote" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ "semver", ] [[package]] name = "scoped-tls" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" [[package]] name = "semver" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" [[package]] name = "signal-hook" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-registry" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] [[package]] name = "slab" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "socket2" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" dependencies = [ "libc", "winapi", ] [[package]] name = "syn" version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "value-bag" version = "1.0.0-alpha.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" dependencies = [ "ctor", "version_check", ] [[package]] name = "version_check" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "waker-fn" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "wasm-bindgen" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" dependencies = [ "bumpalo", "lazy_static", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" dependencies = [ "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" [[package]] name = "wasm-bindgen-test" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96f1aa7971fdf61ef0f353602102dbea75a56e225ed036c1e3740564b91e6b7e" dependencies = [ "console_error_panic_hook", "js-sys", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", ] [[package]] name = "wasm-bindgen-test-macro" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6006f79628dfeb96a86d4db51fbf1344cd7fd8408f06fc9aa3c84913a4789688" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "web-sys" version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "wepoll-ffi" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" dependencies = [ "cc", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" pharos-0.5.3/Cargo.toml0000644000000031420000000000100103330ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "pharos" version = "0.5.3" authors = ["Naja Melan "] exclude = ["tests", "examples", "ci", ".travis.yml", "TODO.md", "CONTRIBUTING.md"] description = "Observer pattern which generates a futures 0.3 stream of events" documentation = "https://docs.rs/pharos" readme = "README.md" keywords = ["observer", "futures", "stream", "broadcast", "publish_subscribe"] categories = ["asynchronous"] license = "Unlicense" repository = "https://github.com/najamelan/pharos" resolver = "2" [package.metadata.docs.rs] all-features = true targets = [] [profile.release] codegen-units = 1 [dependencies.futures] version = "^0.3" features = ["std"] default-features = false [dev-dependencies.assert_matches] version = "^1" [dev-dependencies.async-std] version = "^1" features = ["attributes"] [dev-dependencies.async_executors] version = "^0.4" features = ["async_std"] [dev-dependencies.futures] version = "^0.3" [dev-dependencies.wasm-bindgen-test] version = "^0.3" [build-dependencies.rustc_version] version = "^0.4" [badges.maintenance] status = "actively-developed" [badges.travis-ci] repository = "najamelan/pharos" pharos-0.5.3/Cargo.toml.orig000064400000000000000000000022550072674642500140500ustar 00000000000000# Auto-generated from "Cargo.yml" [badges] [badges.maintenance] status = "actively-developed" [badges.travis-ci] repository = "najamelan/pharos" [build-dependencies] rustc_version = "^0.4" [dependencies] [dependencies.futures] default-features = false features = ["std"] version = "^0.3" [dev-dependencies] assert_matches = "^1" futures = "^0.3" wasm-bindgen-test = "^0.3" [dev-dependencies.async-std] features = ["attributes"] version = "^1" [dev-dependencies.async_executors] features = ["async_std"] version = "^0.4" [package] authors = ["Naja Melan "] categories = ["asynchronous"] description = "Observer pattern which generates a futures 0.3 stream of events" documentation = "https://docs.rs/pharos" edition = "2021" exclude = ["tests", "examples", "ci", ".travis.yml", "TODO.md", "CONTRIBUTING.md"] keywords = ["observer", "futures", "stream", "broadcast", "publish_subscribe"] license = "Unlicense" name = "pharos" readme = "README.md" repository = "https://github.com/najamelan/pharos" version = "0.5.3" [package.metadata] [package.metadata.docs] [package.metadata.docs.rs] all-features = true targets = [] [profile] [profile.release] codegen-units = 1 pharos-0.5.3/Cargo.yml000064400000000000000000000040310072674642500127310ustar 00000000000000package: # When releasing to crates.io: # # - last check for all TODO, FIXME, expect, unwrap. # - recheck log statements (informative, none left that were just for development, ...) # - `cargo +nightly doc` and re-read and final polish of documentation. # # - Update CHANGELOG.md. # - Update version numbers in Cargo.yml, Cargo.toml, install section of readme. # # - `touch **.rs && cargo clippy --tests --examples --benches --all-features` # - `cargo update` # - `cargo udeps --all-targets --all-features` # - `cargo audit` # - `cargo crev crate verify --show-all --recursive` and review. # - 'cargo test --all-targets --all-features' # # - push dev and verify CI result # - `cargo test` on dependent crates # # - cargo publish # - `git checkout master && git merge dev --no-ff` # - `git tag x.x.x` with version number. # - `git push && git push --tags` # version : 0.5.3 name : pharos authors : [ Naja Melan ] edition : '2021' readme : README.md license : Unlicense repository : https://github.com/najamelan/pharos documentation : https://docs.rs/pharos description : Observer pattern which generates a futures 0.3 stream of events categories : [ asynchronous ] keywords : [ observer, futures, stream, broadcast, publish_subscribe ] exclude : [ tests, examples, ci, .travis.yml, TODO.md, CONTRIBUTING.md ] metadata: docs: rs: all-features: true targets : [] badges: travis-ci : { repository: najamelan/pharos } maintenance : { status : actively-developed } dependencies: futures: { version: ^0.3, default-features: false, features: [std] } dev-dependencies: futures : ^0.3 assert_matches : ^1 async-std : { version: ^1 , features: [ attributes ] } async_executors : { version: ^0.4, features: [ async_std ] } wasm-bindgen-test : ^0.3 build-dependencies: rustc_version: ^0.4 profile: release: codegen-units: 1 pharos-0.5.3/README.md000064400000000000000000000226220072674642500124400ustar 00000000000000# pharos [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) [![Build Status](https://github.com/najamelan/pharos/workflows/ci/badge.svg?branch=master)](https://github.com/najamelan/pharos/actions) [![Docs](https://docs.rs/pharos/badge.svg)](https://docs.rs/pharos) [![Crates.io](https://img.shields.io/crates/v/pharos)](https://crates.io/crates/pharos) [![Crates.io downloads](https://img.shields.io/crates/d/pharos)](https://crates.io/crates/pharos) > An introduction to pharos is available in many formats: [video](https://youtu.be/BAzsxW-nxh8), [wikipedia](https://en.wikipedia.org/wiki/Lighthouse_of_Alexandria) and it was even honored by many artists like [this painting by Micheal Turner](http://omeka.wustl.edu/omeka/files/original/2694d12580166e77d40afd37b492a78e.jpg). More seriously, pharos is a small [observer](https://en.wikipedia.org/wiki/Observer_pattern) library that let's you create futures 0.3 streams that observers can listen to. I created it to leverage interoperability we can create by using async [Stream](https://docs.rs/futures-preview/0.3.0-alpha.19/futures/stream/trait.Stream.html) and [Sink](https://docs.rs/futures-preview/0.3.0-alpha.19/futures/sink/trait.Sink.html) from the futures library. So you can use all stream combinators, forward it into Sinks and so on. Minimal rustc version: 1.39. ## Table of Contents - [Security](#security) - [Limitations](#limitations) - [Future work](#future-work) - [Install](#install) - [Upgrade](#upgrade) - [Dependencies](#dependencies) - [Usage](#usage) - [Filter](#filter) - [API](#api) - [Contributing](#contributing) - [Code of Conduct](#code-of-conduct) - [License](#license) ## Security The main issue with this crate right now is the possibility for the observable to outpace the observer. When using bounded channels, there is back pressure, which might allow DDOS attacks if using the pattern on arriving network packets. When using the unbounded channels, it might lead to excessive memory consumption if observers are outpaced. TODO: To mitigate these problems effectively, I will add a ring channel where the channel will only buffer a certain amount events and will overwrite the oldest event instead of blocking the sender when the buffer is full. This crate has: `#![ forbid( unsafe_code ) ]`, but it's dependency (futures library) uses a lot of unsafe code. ### Limitations - only bounded and unbounded channel as back-end (for now) - [`Events`] is not clonable right now (would require support from the channels we use as back-ends, eg. broadcast type channel) - performance tweaking still needs to be done ### Future work Please check out the [todo](https://github.com/najamelan/pharos/blob/master/TODO.md) for ambitions. ## Install With [cargo add](https://github.com/killercup/cargo-edit): `cargo add pharos` With [cargo yaml](https://gitlab.com/storedbox/cargo-yaml): ```yaml dependencies: pharos: ^0.5 ``` With raw Cargo.toml ```toml [dependencies] pharos = "0.5" ``` ### Upgrade Please check out the [changelog](https://github.com/najamelan/pharos/blob/master/CHANGELOG.md) when upgrading. ### Dependencies This crate only has but one dependency. Cargo will automatically handle it for you. This dependency contains `unsafe` code. ```yaml dependencies: futures: { version: ^0.3, default-features: false } ``` ## Usage `pharos` only works from async code, implementing Sink to notify observers. You can notify observers from within `poll_*` methods by calling the poll methods of the [Sink](https://docs.rs/futures-preview/0.3.0-alpha.19/futures/sink/trait.Sink.html) impl directly. In async context you can use [SinkExt::send](https://docs.rs/futures-preview/0.3.0-alpha.19/futures/sink/trait.SinkExt.html#method.send). Observers must consume the messages fast enough, otherwise they will slow down the observable (bounded channel) or cause memory leak (unbounded channel). Whenever observers want to unsubscribe, they can just drop the stream or call `close` on it. If you are an observable and you want to notify observers that no more messages will follow, just drop the pharos object. Failing that, create an event type that signifies EOF and send that to observers. Your event type will be cloned once for each observer, so you might want to put it in an Arc if it's bigger than 2 pointer sizes (eg. there's no point putting an enum without data in an Arc). When you need to notify a pharos object from several async tasks, you can use [`SharedPharos`]. This type allows observing and notifying with a shared reference and handles synchronyzation internally. Examples can be found in the [examples](https://github.com/najamelan/pharos/tree/master/examples) directory. Here is the most basic one: ```rust use { pharos :: { * } , futures :: { executor::block_on, StreamExt, SinkExt } , }; // here we put a pharos object on our struct // struct Goddess { pharos: Pharos } impl Goddess { fn new() -> Self { Self { pharos: Pharos::default() } } // Send Goddess sailing so she can tweet about it! // pub async fn sail( &mut self ) { // It's infallible. Observers that error will be dropped, since the only kind of errors on // channels are when the channel is closed. // self.pharos.send( GoddessEvent::Sailing ).await.expect( "notify observers" ); } } // Event types need to implement clone, but you can wrap them in Arc if not. Also they will be // cloned, so if you will have several observers and big event data, putting them in an Arc is // definitely best. It has no benefit to put a simple dataless enum in an Arc though. // #[ derive( Clone, Debug, PartialEq, Copy ) ] // enum GoddessEvent { Sailing } // This is the needed implementation of Observable. We might one day have a derive for this, // but it's not so interesting, since you always have to point it to your pharos object, // and when you want to be observable over several types of events, you might want to keep // pharos in a hashmap over type_id, and a derive would quickly become a mess. // impl Observable for Goddess { type Error = PharErr; fn observe( &mut self, options: ObserveConfig) -> Observe< '_, GoddessEvent, Self::Error > { self.pharos.observe( options ) } } #[ async_std::main ] // async fn main() { let mut isis = Goddess::new(); // subscribe, the observe method takes options to let you choose: // - channel type (bounded/unbounded) // - a predicate to filter events // let mut events = isis.observe( Channel::Bounded( 3 ).into() ).await.expect( "observe" ); // trigger an event // isis.sail().await; // read from stream and let's put on the console what the event looks like. // let evt = dbg!( events.next().await.unwrap() ); // After this reads on the event stream will return None. // drop( isis ); assert_eq!( GoddessEvent::Sailing, evt ); assert_eq!( None, events.next().await ); } ``` ### Filter Sometimes you are not interested in all event types an observable can emit. A common use case is only listening for a close event on a network connection. The observe method takes options which let you set the predicate. You can only set one predicate for a given observer. ```rust use pharos::*; #[ derive( Clone, Debug, PartialEq, Copy ) ] // enum NetworkEvent { Open , Error , Closing , Closed , } struct Connection { pharos: Pharos } impl Observable for Connection { type Error = PharErr; fn observe( &mut self, options: ObserveConfig) -> Observe< '_, NetworkEvent, Self::Error > { self.pharos.observe( options ) } } #[ async_std::main ] // async fn main() { let mut conn = Connection{ pharos: Pharos::default() }; // We will only get close events. Note that here we don't need access to any surrounding variables in // the closure, so we can use a function pointer which avoids having to box the closure. // // Filter also has a variant `Closure` which allows you to pass in a `Box bool + Send>` // if you need access to surrounding context to make the decision. // let filter = Filter::Pointer( |e| e == &NetworkEvent::Closed ); // By creating the config object through into, other options will be defaults, notably here // this will use unbounded channels. // let observer = conn.observe( filter.into() ).await.expect( "observe" ); // Combine both options. // let filter = Filter::Pointer( |e| e != &NetworkEvent::Closed ); let opts = ObserveConfig::from( filter ).channel( Channel::Bounded(5) ); // Get everything but close events over a bounded channel with queue size 5. // let bounded_observer = conn.observe( opts ).await.expect( "observe" ); } ``` ## API API documentation can be found on [docs.rs](https://docs.rs/pharos). ## Contributing Please check out the [contribution guidelines](https://github.com/najamelan/pharos/blob/master/CONTRIBUTING.md). ### Code of conduct Any of the behaviors described in [point 4 "Unacceptable Behavior" of the Citizens Code of Conduct](http://citizencodeofconduct.org/#unacceptable-behavior) are not welcome here and might get you banned. If anyone including maintainers and moderators of the project fail to respect these/your limits, you are entitled to call them out. ## License [Unlicence](https://unlicense.org/) pharos-0.5.3/build.rs000064400000000000000000000006700072674642500126250ustar 00000000000000// Detect the rustc channel // use rustc_version::{ version_meta, Channel }; fn main() { // Set cfg flags depending on release channel // match version_meta().unwrap().channel { Channel::Stable => println!( "cargo:rustc-cfg=stable" ), Channel::Beta => println!( "cargo:rustc-cfg=beta" ), Channel::Nightly => println!( "cargo:rustc-cfg=nightly" ), Channel::Dev => println!( "cargo:rustc-cfg=rustc_dev" ), } } pharos-0.5.3/deny.toml000064400000000000000000000210600072674642500130100ustar 00000000000000# This template contains all of the possible sections and their default values # Note that all fields that take a lint level have these possible values: # * deny - An error will be produced and the check will fail # * warn - A warning will be produced, but the check will not fail # * allow - No warning or error will be produced, though in some cases a note # will be # The values provided in this template are the default values that will be used # when any section or field is not specified in your own configuration # If 1 or more target triples (and optionally, target_features) are specified, # only the specified targets will be checked when running `cargo deny check`. # This means, if a particular package is only ever used as a target specific # dependency, such as, for example, the `nix` crate only being used via the # `target_family = "unix"` configuration, that only having windows targets in # this list would mean the nix crate, as well as any of its exclusive # dependencies not shared by any other crates, would be ignored, as the target # list here is effectively saying which targets you are building for. targets = [ # The triple can be any string, but only the target triples built in to # rustc (as of 1.40) can be checked against actual config expressions #{ triple = "x86_64-unknown-linux-musl" }, # You can also specify which target_features you promise are enabled for a # particular target. target_features are currently not validated against # the actual valid features supported by the target architecture. #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, ] # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html [advisories] # The path where the advisory database is cloned/fetched into db-path = "~/.cargo/advisory-db" # The url of the advisory database to use db-urls = [ "https://github.com/rustsec/advisory-db" ] # The lint level for security vulnerabilities vulnerability = "deny" # The lint level for unmaintained crates unmaintained = "warn" # The lint level for crates that have been yanked from their source registry yanked = "warn" # The lint level for crates with security notices. Note that as of # 2019-12-17 there are no security notice advisories in # https://github.com/rustsec/advisory-db notice = "warn" # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ #"RUSTSEC-0000-0000", ] # Threshold for security vulnerabilities, any vulnerability with a CVSS score # lower than the range specified will be ignored. Note that ignored advisories # will still output a note when they are encountered. # * None - CVSS Score 0.0 # * Low - CVSS Score 0.1 - 3.9 # * Medium - CVSS Score 4.0 - 6.9 # * High - CVSS Score 7.0 - 8.9 # * Critical - CVSS Score 9.0 - 10.0 #severity-threshold = # This section is considered when running `cargo deny check licenses` # More documentation for the licenses section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] # The lint level for crates which do not have a detectable license unlicensed = "deny" # List of explictly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. allow = [ "MIT", "Apache-2.0", # "Apache-2.0 WITH LLVM-exception", "Unlicense", ] # List of explictly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. deny = [ #"Nokia", ] # Lint level for licenses considered copyleft copyleft = "allow" # Blanket approval or denial for OSI-approved or FSF Free/Libre licenses # * both - The license will be approved if it is both OSI-approved *AND* FSF # * either - The license will be approved if it is either OSI-approved *OR* FSF # * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF # * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved # * neither - This predicate is ignored and the default lint level is used allow-osi-fsf-free = "either" # Lint level used when no other predicates are matched # 1. License isn't in the allow or deny lists # 2. License isn't copyleft # 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" default = "deny" # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the # canonical license text of a valid SPDX license file. # [possible values: any between 0.0 and 1.0]. confidence-threshold = 0.8 # Allow 1 or more licenses on a per-crate basis, so that particular licenses # aren't accepted for every possible crate as with the normal allow list exceptions = [ # Each entry is the crate and version constraint, and its specific allow # list #{ allow = ["Zlib"], name = "adler32", version = "*" }, ] # Some crates don't have (easily) machine readable licensing information, # adding a clarification entry for it allows you to manually specify the # licensing information #[[licenses.clarify]] # The name of the crate the clarification applies to #name = "ring" # THe optional version constraint for the crate #version = "*" # The SPDX expression for the license requirements of the crate #expression = "MIT AND ISC AND OpenSSL" # One or more files in the crate's source used as the "source of truth" for # the license expression. If the contents match, the clarification will be used # when running the license check, otherwise the clarification will be ignored # and the crate will be checked normally, which may produce warnings or errors # depending on the rest of your configuration #license-files = [ # Each entry is a crate relative path, and the (opaque) hash of its contents #{ path = "LICENSE", hash = 0xbd0eed23 } #] [licenses.private] # If true, ignores workspace crates that aren't published, or are only # published to private registries ignore = false # One or more private registries that you might publish crates to, if a crate # is only published to private registries, and ignore is true, the crate will # not have its license(s) checked registries = [ #"https://sekretz.com/registry ] # This section is considered when running `cargo deny check bans`. # More documentation about the 'bans' section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html [bans] # Lint level for when multiple versions of the same crate are detected multiple-versions = "warn" # The graph highlighting used when creating dotgraphs for crates # with multiple versions # * lowest-version - The path to the lowest versioned duplicate is highlighted # * simplest-path - The path to the version with the fewest edges is highlighted # * all - Both lowest-version and simplest-path are used highlight = "all" # List of crates that are allowed. Use with care! allow = [ #{ name = "ansi_term", version = "=0.11.0" }, ] # List of crates to deny deny = [ # Each entry the name of a crate and a version range. If version is # not specified, all versions will be matched. { name = "plutonium" }, # Crate intended to hide unsafe usage. { name = "fake-static" }, # Crate intended to demonstrate soundness bug in safe code. ] # Certain crates/versions that will be skipped when doing duplicate detection. skip = [ #{ name = "ansi_term", version = "=0.11.0" }, ] # Similarly to `skip` allows you to skip certain crates during duplicate # detection. Unlike skip, it also includes the entire tree of transitive # dependencies starting at the specified crate, up to a certain depth, which is # by default infinite skip-tree = [ #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, ] # This section is considered when running `cargo deny check sources`. # More documentation about the 'sources' section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html [sources] # Lint level for what to happen when a crate from a crate registry that is not # in the allow list is encountered unknown-registry = "warn" # Lint level for what to happen when a crate from a git repository that is not # in the allow list is encountered unknown-git = "warn" # List of URLs for allowed crate registries. Defaults to the crates.io index # if not specified. If it is specified but empty, no registries are allowed. allow-registry = ["https://github.com/rust-lang/crates.io-index"] # List of URLs for allowed Git repositories allow-git = [] pharos-0.5.3/src/error.rs000064400000000000000000000047220072674642500134500ustar 00000000000000use crate::{ import::* }; /// The error type for errors happening in `pharos`. /// /// Use [`PharErr::kind()`] to know which kind of error happened. // #[ derive( Debug ) ] // pub struct PharErr { pub(crate) inner: Option< Box >, pub(crate) kind : ErrorKind, } impl PharErr { /// Identify which error happened. // pub fn kind( &self ) -> &ErrorKind { &self.kind } } impl From for PharErr { fn from( kind: ErrorKind ) -> Self { Self { inner: None, kind } } } impl From for PharErr { fn from( inner: FutSendError ) -> Self { Self { inner: Some( Box::new( inner ) ), kind: ErrorKind::SendError } } } /// The different kind of errors that can happen when you use the `pharos` API. // #[ non_exhaustive ] #[ derive( Debug, Copy, Clone, PartialEq, Eq ) ] // pub enum ErrorKind { #[ doc( hidden ) ] // //This variant is only used internally. // SendError, /// The pharos object is already closed. You can no longer send messages or observe it. /// This should only happen if you call [SinkExt::close](https://docs.rs/futures-preview/0.3.0-alpha.19/futures/sink/trait.SinkExt.html#method.close) on it. // Closed, /// The minimum valid buffer size for [`Channel::Bounded`](crate::observable::Channel) is `1`, you sent in `0`. // MinChannelSizeOne, } impl PartialEq<&ErrorKind> for ErrorKind { fn eq( &self, other: &&ErrorKind ) -> bool { self == *other } } impl PartialEq for &ErrorKind { fn eq( &self, other: &ErrorKind ) -> bool { *self == other } } impl ErrorTrait for PharErr { fn source( &self ) -> Option< &(dyn ErrorTrait + 'static) > { // Somehow using as_deref gives us thread bound problems... // #[ allow( clippy::option_as_ref_deref ) ] // self.inner.as_ref().map( |e| -> &(dyn ErrorTrait + 'static) { e.deref() } ) } } impl fmt::Display for ErrorKind { fn fmt( &self, f: &mut fmt::Formatter<'_> ) -> fmt::Result { match self { Self::SendError => fmt::Display::fmt( "Channel closed.", f ) , Self::MinChannelSizeOne => fmt::Display::fmt( "The minimum valid buffer size for Channel::Bounded is 1, you send in 0.", f ) , _ => unreachable!(), } } } impl fmt::Display for PharErr { fn fmt( &self, f: &mut fmt::Formatter<'_> ) -> fmt::Result { let inner = match self.source() { Some(e) => format!( " Caused by: {}", e ), None => String::new() , }; write!( f, "pharos::PharErr: {}{}", self.kind, inner ) } } pharos-0.5.3/src/events.rs000064400000000000000000000141460072674642500136240ustar 00000000000000use crate :: { import::*, Filter, ObserveConfig, observable::Channel, PharErr, ErrorKind }; /// A stream of events. This is returned from [Observable::observe](crate::Observable::observe). /// You will only start receiving events from the moment you call this. Any events in the observed /// object emitted before will not be delivered. /// /// For pharos 0.4.0 on x64 Linux: `std::mem::size_of::>() == 16` // #[ derive( Debug ) ] // pub struct Events where Event: Clone + 'static + Send { rx: Receiver, } impl Events where Event: Clone + 'static + Send { pub(crate) fn new( config: ObserveConfig ) -> (Self, Sender) { let (tx, rx) = match config.channel { Channel::Bounded( queue_size ) => { let (tx, rx) = mpsc::channel( queue_size - 1 ); ( Sender::Bounded{ tx, filter: config.filter }, Receiver::Bounded{ rx } ) } Channel::Unbounded => { let (tx, rx) = mpsc::unbounded(); ( Sender::Unbounded{ tx, filter: config.filter }, Receiver::Unbounded{ rx } ) } _ => unreachable!(), }; ( Self{ rx }, tx ) } /// Disconnect from the observable object. This way the sender will stop sending new events /// and you can still continue to read any events that are still pending in the channel. // pub fn close( &mut self ) { self.rx.close(); } } // Just forward // impl Stream for Events where Event: Clone + 'static + Send { type Item = Event; fn poll_next( mut self: Pin<&mut Self>, cx: &mut Context<'_> ) -> Poll< Option > { Pin::new( &mut self.rx ).poll_next( cx ) } } /// The sender of the channel. /// For pharos 0.4.0 on x64 Linux: `std::mem::size_of::>() == 56` // pub(crate) enum Sender where Event: Clone + 'static + Send { Bounded { tx: FutSender , filter: Option> } , Unbounded{ tx: FutUnboundedSender, filter: Option> } , } impl Sender where Event: Clone + 'static + Send { // Verify whether this observer is still around. // pub(crate) fn is_closed( &self ) -> bool { match self { Sender::Bounded { tx, .. } => tx.is_closed(), Sender::Unbounded{ tx, .. } => tx.is_closed(), } } /// Check whether this sender is interested in this event. // pub(crate) fn filter( &mut self, evt: &Event ) -> bool { match self { Sender::Bounded { filter, .. } => Self::filter_inner( filter, evt ), Sender::Unbounded{ filter, .. } => Self::filter_inner( filter, evt ), } } fn filter_inner( filter: &mut Option>, evt: &Event ) -> bool { match filter { Some(f) => f.call(evt), None => true , } } } /// The receiver of the channel, abstracting over different channel types. // enum Receiver where Event: Clone + 'static + Send { Bounded { rx: FutReceiver } , Unbounded{ rx: FutUnboundedReceiver } , } impl Receiver where Event: Clone + 'static + Send { fn close( &mut self ) { match self { Receiver::Bounded { rx } => rx.close(), Receiver::Unbounded{ rx } => rx.close(), }; } } impl fmt::Debug for Receiver where Event: 'static + Clone + Send { fn fmt( &self, f: &mut fmt::Formatter<'_> ) -> fmt::Result { match self { Self::Bounded {..} => write!( f, "pharos::events::Receiver::<{}>::Bounded(_)" , type_name::() ), Self::Unbounded{..} => write!( f, "pharos::events::Receiver::<{}>::Unbounded(_)", type_name::() ), } } } impl Stream for Receiver where Event: Clone + 'static + Send { type Item = Event; fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_> ) -> Poll< Option > { match self.get_mut() { Receiver::Bounded { rx } => Pin::new( rx ).poll_next( cx ), Receiver::Unbounded{ rx } => Pin::new( rx ).poll_next( cx ), } } } impl Sink for Sender where Event: Clone + 'static + Send { type Error = PharErr; fn poll_ready( self: Pin<&mut Self>, cx: &mut Context<'_> ) -> Poll> { match self.get_mut() { Sender::Bounded { tx, .. } => Pin::new( tx ).poll_ready( cx ).map_err( Into::into ), Sender::Unbounded{ tx, .. } => Pin::new( tx ).poll_ready( cx ).map_err( Into::into ), } } fn start_send( self: Pin<&mut Self>, item: Event ) -> Result<(), Self::Error> { match self.get_mut() { Sender::Bounded { tx, .. } => Pin::new( tx ).start_send( item ).map_err( Into::into ), Sender::Unbounded{ tx, .. } => Pin::new( tx ).start_send( item ).map_err( Into::into ), } } // Note that on futures-rs bounded channels poll_flush has a problematic implementation. // - it just calls poll_ready, which means it will be pending when the buffer is full. So // it will make SinkExt::send hang, bad! // - it will swallow disconnected errors, so we don't get feedback allowing us to free slots. // // In principle channels are always flushed, because when the message is in the buffer, it's // ready for the reader to read. So this should just be a noop. // // We compensate for the error swallowing by checking `is_closed`. // fn poll_flush( self: Pin<&mut Self>, _cx: &mut Context<'_> ) -> Poll> { match self.get_mut() { Sender::Bounded { tx, .. } => { if tx.is_closed() { Poll::Ready(Err( ErrorKind::Closed.into() ))} else { Poll::Ready(Ok ( () ))} } Sender::Unbounded{ tx, .. } => { if tx.is_closed() { Poll::Ready(Err( ErrorKind::Closed.into() ))} else { Poll::Ready(Ok ( () ))} } } } fn poll_close( self: Pin<&mut Self>, cx: &mut Context<'_> ) -> Poll> { match self.get_mut() { Sender::Bounded { tx, .. } => Pin::new( tx ).poll_close( cx ).map_err( Into::into ), Sender::Unbounded{ tx, .. } => Pin::new( tx ).poll_close( cx ).map_err( Into::into ), } } } #[ cfg( test ) ] // mod tests { use super::*; #[test] // fn debug() { let e = Events::::new( ObserveConfig::default() ); assert_eq!( "Events { rx: pharos::events::Receiver::::Unbounded(_) }", &format!( "{:?}", e.0 ) ); } } pharos-0.5.3/src/filter.rs000064400000000000000000000051340072674642500136020ustar 00000000000000use crate :: { import::* }; /// Predicate for filtering events. /// /// This is an enum because closures that capture variables from /// their environment need to be boxed. More often than not, an event will be a simple enum and /// the predicate will just match on the variant, so it would be wasteful to impose boxing in those /// cases, hence there is a function pointer variant which does not require boxing. This should /// be preferred where possible. /// /// ``` /// use pharos::*; /// /// let a = 5; /// /// // This closure captures the a variable from it's environment. /// // We can still use it as a filter by boxing it with `closure`. /// // /// // Note: it depends on the circumstances, but often enough, we need to /// // annotate the type of the event parameter to the predicate. /// // /// // For this example we use bool as event type for simplicity, but it works /// // just the same if that's an enum. /// // /// let predicate = move |_: &bool| { a; true }; /// /// let filter = Filter::Closure( Box::new(predicate) ); /// /// // This one does not capture anything, so it can be stored as a function pointer /// // without boxing. /// // /// let predicate = move |_: &bool| { true }; /// /// let filter = Filter::Pointer( predicate ); /// /// // You can also use actual functions as filters. /// // /// fn predicate_function( event: &bool ) -> bool { true } /// /// let filter = Filter::Pointer( predicate_function ); /// ``` // pub enum Filter where Event: Clone + 'static + Send , { /// A function pointer to a predicate to filter events. // Pointer( fn(&Event) -> bool ), /// A boxed closure to a predicate to filter events. // Closure( Box bool + Send> ), } impl Filter where Event: Clone + 'static + Send { /// Invoke the predicate. // pub(crate) fn call( &mut self, evt: &Event ) -> bool { match self { Self::Pointer(f) => f(evt), Self::Closure(f) => f(evt), } } } impl fmt::Debug for Filter where Event: Clone + 'static + Send { fn fmt( &self, f: &mut fmt::Formatter<'_> ) -> fmt::Result { match self { Self::Pointer(_) => write!( f, "pharos::Filter<{}>::Pointer(_)", type_name::() ), Self::Closure(_) => write!( f, "pharos::Filter<{}>::Closure(_)", type_name::() ), } } } #[ cfg( test ) ] // mod tests { use super::*; #[test] // fn debug() { let f = Filter::Pointer( |b| *b ); let g = Filter::Closure( Box::new( |b| *b ) ); assert_eq!( "pharos::Filter::Pointer(_)", &format!( "{:?}", f ) ); assert_eq!( "pharos::Filter::Closure(_)", &format!( "{:?}", g ) ); } } pharos-0.5.3/src/lib.rs000064400000000000000000000046600072674642500130660ustar 00000000000000#![ cfg_attr( nightly, feature( doc_cfg ) ) ] #![ doc = include_str!( "../README.md" ) ] #![ doc ( html_root_url = "https://docs.rs/pharos" ) ] #![ deny ( missing_docs ) ] #![ forbid ( unsafe_code ) ] #![ allow ( clippy::suspicious_else_formatting ) ] #![ warn ( missing_debug_implementations , missing_docs , nonstandard_style , rust_2018_idioms , trivial_casts , trivial_numeric_casts , unused_extern_crates , unused_qualifications , single_use_lifetimes , unreachable_pub , variant_size_differences , )] mod error ; mod events ; mod observable ; mod pharos ; mod filter ; mod shared_pharos ; pub use { self::pharos :: { Pharos } , filter :: { Filter } , observable :: { Observable, ObservableLocal, ObserveConfig, Channel } , events :: { Events } , error :: { PharErr, ErrorKind } , shared_pharos:: { SharedPharos } , }; mod import { pub(crate) use { std :: { fmt, error::Error as ErrorTrait, ops::Deref, any::type_name } , std :: { task::{ Poll, Context }, pin::Pin, future::Future, sync::Arc } , futures :: { Stream, Sink, SinkExt, ready, future::FutureExt, lock::Mutex } , futures::channel::mpsc:: { self , Sender as FutSender , Receiver as FutReceiver , UnboundedSender as FutUnboundedSender , UnboundedReceiver as FutUnboundedReceiver , SendError as FutSendError , }, }; #[ cfg( test ) ] // pub(crate) use { assert_matches :: { assert_matches } , futures :: { future::poll_fn, executor::block_on } , }; } use import::*; /// A pinned boxed future returned by the Observable::observe method. // pub type Observe<'a, Event, Error> = Pin, Error> > + 'a + Send >>; /// A pinned boxed future returned by the ObservableLocal::observe_local method. // pub type ObserveLocal<'a, Event, Error> = Pin, Error> > + 'a >>; pharos-0.5.3/src/observable.rs000064400000000000000000000175130072674642500144450ustar 00000000000000use crate :: { Filter, Observe, ObserveLocal }; /// Indicate that a type is observable. You can call [`observe`](Observable::observe) to get a /// stream of events. /// /// Generally used with a [Pharos](crate::Pharos) object which manages the observers for you. /// /// ``` /// use pharos::*; /// use futures::stream::StreamExt; /// /// // The event we want to broadcast /// // /// #[ derive( Debug, Clone ) ] /// // /// enum Steps /// { /// Step1 , /// Step2 , /// Done , /// /// // Data is possible, but it has to be clone and will be cloned for each observer /// // except observers that filter this event out. /// // /// Error(u8) , /// } /// /// /// impl Steps /// { /// // We can use this as a predicate to filter events. /// // /// fn is_err( &self ) -> bool /// { /// match self /// { /// Self::Error(_) => true , /// _ => false , /// } /// } /// } /// /// /// // The object we want to be observable. /// // /// struct Foo { pharos: Pharos }; /// /// impl Observable for Foo /// { /// type Error = PharErr; /// /// // Pharos implements observable, so we just forward the call. /// // /// fn observe( &mut self, options: ObserveConfig ) -> Observe< '_, Steps, Self::Error> /// { /// self.pharos.observe( options ) /// } /// } /// /// /// // use in async context /// // /// async fn task() /// { /// let mut foo = Foo { pharos: Pharos::default() }; /// let mut errors = foo.observe( Filter::Pointer( Steps::is_err ).into() ).await.expect( "observe" ); /// /// // will only be notified on errors thanks to the filter. /// // /// let next_error = errors.next().await; /// } /// ``` // pub trait Observable where Event: Clone + 'static + Send , { /// The error type that is returned if observing is not possible. /// /// [Pharos](crate::Pharos) implements /// [Sink](https://docs.rs/futures-preview/0.3.0-alpha.19/futures/sink/trait.Sink.html) /// which has a close method, so observing will no longer be possible after close is called. /// /// Other than that, you might want to have moments in your objects lifetime when you don't want to take /// any more observers. Returning a result from [observe](Observable::observe) enables that. /// /// You can of course map the error of pharos to your own error type. // type Error: std::error::Error; /// Add an observer to the observable. Options allow chosing the channel type and /// to filter events with a predicate. // fn observe( &mut self, options: ObserveConfig ) -> Observe<'_, Event, Self::Error >; } /// Like Observable, but the future returned is not `Send`, thus the observable type does not need to be `Send`. // pub trait ObservableLocal where Event: Clone + 'static + Send , { /// The error type that is returned if observing is not possible. /// /// [Pharos](crate::Pharos) implements /// [Sink](https://docs.rs/futures-preview/0.3.0-alpha.19/futures/sink/trait.Sink.html) /// which has a close method, so observing will no longer be possible after close is called. /// /// Other than that, you might want to have moments in your objects lifetime when you don't want to take /// any more observers. Returning a result from [observe](Observable::observe) enables that. /// /// You can of course map the error of pharos to your own error type. // type Error: std::error::Error; /// Add an observer to the observable. Options allow chosing the channel type and /// to filter events with a predicate. // fn observe_local( &mut self, options: ObserveConfig ) -> ObserveLocal<'_, Event, Self::Error >; } /// Choose the type of channel that will be used for your event stream. Used in [ObserveConfig]. // #[ derive( Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord )] // pub enum Channel { /// A channel with a limited message queue (the usize parameter). Creates back pressure when the buffer is full. /// This means that producer tasks may block if consumers can't process fast enough. /// /// The minimum valid buffer size is 1. // Bounded(usize), /// A channel with unbounded capacity. Note that this may lead to unbounded memory consumption if producers /// outpace consumers. // Unbounded, /// This enum might grow in the future, thanks to this that won't be a breaking change. // __NonExhaustive__ } impl Default for Channel { fn default() -> Self { Channel::Unbounded } } /// Configuration for your event stream. /// /// Pass to [Observable::observe] when subscribing. This let's you choose the type of [channel](Channel) and let's /// you set a [filter](Filter) to ignore certain events. /// /// ``` /// use pharos::*; /// /// // We choose event type usize for simplicity. You choose whatever type you want here, /// // see the bounds on the Event type parameter throughout this library. /// // /// let mut pharos = Pharos::::default(); /// /// // Use defaults, unbounded channel and no filter. /// // /// pharos.observe( ObserveConfig::default() ); /// /// // Use bounded channel and defaults for other options. /// // /// pharos.observe( Channel::Bounded(5).into() ); /// /// // Use a filter and defaults for other options. /// // Will only receive events if they are bigger than three. /// // /// pharos.observe( Filter::Pointer( |evt| *evt > 3 ).into() ); /// /// // Set both channel and filter. Note you can only set one filter per observable. /// // /// let opts = ObserveConfig::default() /// /// .channel( Channel::Bounded( 5 ) ) /// .filter ( |evt| *evt > 3 ) /// ; /// /// pharos.observe( opts ); /// ``` // #[ derive( Debug ) ] // pub struct ObserveConfig where Event: Clone + 'static + Send { pub(crate) channel: Channel, pub(crate) filter : Option>, } /// Create a default configuration: /// - no filter /// - an unbounded channel // impl Default for ObserveConfig where Event: Clone + 'static + Send { fn default() -> Self { Self { channel: Channel::default(), filter : None , } } } impl ObserveConfig where Event: Clone + 'static + Send { /// Choose which channel implementation to use for your event stream. // pub fn channel( mut self, channel: Channel ) -> Self { self.channel = channel; self } /// Filter your event stream with a predicate that is a fn pointer. /// You can only set one filter per observable. // pub fn filter( mut self, filter: fn(&Event) -> bool ) -> Self { debug_assert!( self.filter.is_none(), "You can only set one filter on ObserveConfig" ); self.filter = Some( Filter::Pointer(filter) ); self } /// Filter your event stream with a predicate that is a closure that captures environment. /// It is preferred to use [filter](ObserveConfig::filter) if you can as this will box the closure. /// You can only set one filter per observable. // pub fn filter_boxed( mut self, filter: impl FnMut(&Event) -> bool + Send + 'static ) -> Self { debug_assert!( self.filter.is_none(), "You can only set one filter on ObserveConfig" ); self.filter = Some( Filter::Closure( Box::new(filter) ) ); self } } /// Create a [ObserveConfig] from a [Channel], getting default values for other options. // impl From for ObserveConfig where Event: Clone + 'static + Send { fn from( channel: Channel ) -> Self { Self::default().channel( channel ) } } /// Create a [ObserveConfig] from a [Filter], getting default values for other options. // impl From> for ObserveConfig where Event: Clone + 'static + Send { fn from( filter: Filter ) -> Self { Self { filter: Some(filter), ..Self::default() } } } pharos-0.5.3/src/pharos.rs000064400000000000000000000364600072674642500136170ustar 00000000000000use crate :: { import::*, Observable, Observe, Events, ObserveConfig, events::Sender, PharErr, ErrorKind, Channel }; /// The Pharos lighthouse. When you implement [Observable] on your type, you can forward /// the [`observe`](Observable::observe) method to Pharos and use [SinkExt::send](https://docs.rs/futures-preview/0.3.0-alpha.19/futures/sink/trait.SinkExt.html#method.send) to notify observers. /// /// You can of course create several `Pharos` (I know, historical sacrilege) for (different) types /// of events. /// /// Please see the docs for [Observable] for an example. Others can be found in the README and /// the [examples](https://github.com/najamelan/pharos/tree/master/examples) directory of the repository. /// /// ## Implementation. /// /// Currently just holds a `Vec>`. It will drop observers if the channel has /// returned an error, which means it is closed or disconnected. However, we currently don't /// compact the vector. Slots are reused for new observers, but the vector never shrinks. /// /// **Note**: we only detect that observers can be removed when [SinkExt::send](https://docs.rs/futures-preview/0.3.0-alpha.19/futures/sink/trait.SinkExt.html#method.send) or [Pharos::num_observers] /// is being called. Otherwise, we won't find out about disconnected observers and the vector of observers /// will not mark deleted observers and thus their slots can not be reused. /// /// The [Sink](https://docs.rs/futures-preview/0.3.0-alpha.19/futures/sink/trait.Sink.html) impl /// is not very optimized for the moment. It just loops over all observers in each poll method /// so it will call `poll_ready` and `poll_flush` again for observers that already returned `Poll::Ready(Ok(()))`. /// /// TODO: I will do some benchmarking and see if this can be improved, eg. by keeping a state which tracks which /// observers we still have to poll. // pub struct Pharos where Event: 'static + Clone + Send { // Observers never get moved. Their index stays stable, so that when we free a slot, // we can store that in `free_slots`. // observers : Vec >>, free_slots: Vec , closed : bool , } impl fmt::Debug for Pharos where Event: 'static + Clone + Send { fn fmt( &self, f: &mut fmt::Formatter<'_> ) -> fmt::Result { write!( f, "pharos::Pharos<{}>", type_name::() ) } } impl Pharos where Event: 'static + Clone + Send { /// Create a new Pharos. May it's light guide you to safe harbor. /// /// You can set the initial capacity of the vector of observers, if you know you will a lot of observers /// it will save allocations by setting this to a higher number. /// /// For pharos 0.4.0 on x64 Linux: `std::mem::size_of::>>() == 56 bytes`. // pub fn new( capacity: usize ) -> Self { Self { observers : Vec::with_capacity( capacity ), free_slots: Vec::with_capacity( capacity ), closed : false , } } /// Returns the size of the vector used to store the observers. Useful for debugging and testing if it /// seems to get to big. // pub fn storage_len( &self ) -> usize { self.observers.len() } /// Returns the number of actual observers that are still listening (have not closed or dropped the [Events]). /// This will loop and it will verify for each if they are closed, clearing them from the internal storage /// if they are closed. This is similar to what notify does, but without sending an event. // pub fn num_observers( &mut self ) -> usize { let mut count = 0; for (i, opt) in self.observers.iter_mut().enumerate() { if let Some(observer) = opt { if !observer.is_closed() { count += 1; } else { self.free_slots.push( i ); *opt = None } } } count } } /// Creates a new pharos, using 10 as the initial capacity of the vector used to store /// observers. If this number does really not fit your use case, call [Pharos::new]. // impl Default for Pharos where Event: 'static + Clone + Send { fn default() -> Self { Self::new( 10 ) } } impl Observable for Pharos where Event: 'static + Clone + Send { type Error = PharErr; /// Will re-use slots from disconnected observers to avoid growing to much. /// /// TODO: provide API for the client to compact the pharos object after reducing the /// number of observers. // fn observe( &mut self, options: ObserveConfig ) -> Observe<'_, Event, Self::Error > { async move { if self.closed { return Err( ErrorKind::Closed.into() ); } if let Channel::Bounded(queue_size) = options.channel { if queue_size < 1 { return Err( ErrorKind::MinChannelSizeOne.into() ); } } let (events, sender) = Events::new( options ); // Try to reuse a free slot // if let Some( i ) = self.free_slots.pop() { self.observers[i] = Some( sender ); } else { self.observers.push( Some( sender ) ); } Ok( events ) }.boxed() } } // See the documentation on Channel for how poll functions work for the channels we use. // impl Sink for Pharos where Event: Clone + 'static + Send { type Error = PharErr; fn poll_ready( self: Pin<&mut Self>, cx: &mut Context<'_> ) -> Poll> { if self.closed { return Err( ErrorKind::Closed.into() ).into(); } // As soon as any is not ready, we are not ready. // // This is a false warning AFAICT. We need to set obs // to None at the end, which is not possible if we have flattened the iterator. // #[allow(clippy::manual_flatten)] // for obs in self.get_mut().observers.iter_mut() { if let Some( ref mut o ) = obs { let res = ready!( Pin::new( o ).poll_ready( cx ) ); // Errors mean disconnected, so drop. // if res.is_err() { // TODO: why don't we add to free_slots here like below? // *obs = None; } } } Ok(()).into() } fn start_send( self: Pin<&mut Self>, evt: Event ) -> Result<(), Self::Error> { if self.closed { return Err( ErrorKind::Closed.into() ); } let this = self.get_mut(); for (i, opt) in this.observers.iter_mut().enumerate() { // if this spot in the vector has a sender // if let Some( obs ) = opt { // if it's closed, let's remove it. // if obs.is_closed() { this.free_slots.push( i ); *opt = None; } // else if it is interested in this event // else if obs.filter( &evt ) { // if sending fails, remove it // if Pin::new( obs ).start_send( evt.clone() ).is_err() { this.free_slots.push( i ); *opt = None; } } } } Ok(()) } fn poll_flush( self: Pin<&mut Self>, cx: &mut Context<'_> ) -> Poll> { if self.closed { return Err( ErrorKind::Closed.into() ).into(); } // We loop over all, polling them all. If any return pending, we return pending. // If any return an error, we drop them. // let mut pending = false; let this = self.get_mut(); for (i, opt) in this.observers.iter_mut().enumerate() { if let Some( ref mut obs ) = opt { match Pin::new( obs ).poll_flush( cx ) { Poll::Pending => pending = true , Poll::Ready(Ok(_)) => continue , Poll::Ready(Err(_)) => { this.free_slots.push( i ); *opt = None; } } } } if pending { Poll::Pending } else { Ok(()).into() } } /// Will close and drop all observers. // fn poll_close( mut self: Pin<&mut Self>, cx: &mut Context<'_> ) -> Poll> { if self.closed { return Ok(()).into(); } else { self.closed = true; } let this = self.get_mut(); for (i, opt) in this.observers.iter_mut().enumerate() { if let Some( ref mut obs ) = opt { let res = ready!( Pin::new( obs ).poll_close( cx ) ); if res.is_err() { this.free_slots.push( i ); *opt = None; } } } Ok(()).into() } } #[ cfg( test ) ] // mod tests { // Tested: // // - ✔ debug impl shows generic type // - ✔ storage length and free slots bookkeeping // - ✔ observe: we actually reuse free slots // - ✔ observe: cannot observe after calling close // - ✔ observe: refuse Channel::Bounded(0) // - ✔ poll_ready have a channel that is full, verify we return pending. // - ✔ poll_ready have a channel that is disconnected, verify we drop it. // - ✔ poll_ready should return closed if the pharos is closed. // - ✔ start_send verify message arrives // - ✔ start_send drop disconnected channel // - ✔ start_send filter message // - ✔ poll_flush drop on error // // TODO: fix the assert_matches ambiguity. Can we use assert!( matches!() ) from std? // use crate :: { *, import::* }; #[test] // fn debug() { let lighthouse = Pharos::::default(); assert_eq!( "pharos::Pharos", &format!( "{:?}", lighthouse ) ); } // #[test] // // // fn size_of_sender() // { // dbg!( std::mem::size_of::>>() ); // dbg!( std::mem::size_of::>() ); // } // verify storage_len and num_observers // #[test] // fn new() { let ph = Pharos::::new( 5 ); assert_eq!( ph.observers.capacity(), 5 ); } // verify storage_len and num_observers // #[async_std::test] // async fn storage_len() { let mut ph = Pharos::::default(); assert_eq!( ph.storage_len (), 0 ); assert_eq!( ph.num_observers (), 0 ); assert_eq!( ph.free_slots.len(), 0 ); let mut a = ph.observe( ObserveConfig::default() ).await.expect( "observe" ); assert_eq!( ph.storage_len (), 1 ); assert_eq!( ph.num_observers (), 1 ); assert_eq!( ph.free_slots.len(), 0 ); let b = ph.observe( ObserveConfig::default() ).await.expect( "observe" ); assert_eq!( ph.storage_len (), 2 ); assert_eq!( ph.num_observers (), 2 ); assert_eq!( ph.free_slots.len(), 0 ); a.close(); assert_eq!( ph.storage_len () , 2 ); assert_eq!( ph.num_observers() , 1 ); assert_eq!( &ph.free_slots , &[0] ); drop( b ); assert_eq!( ph.storage_len (), 2 ); assert_eq!( ph.num_observers(), 0 ); assert_eq!( &ph.free_slots , &[0, 1] ); } // observe: Make sure we are reusing slots // #[async_std::test] // async fn reuse() { let mut ph = Pharos::::default(); let _a = ph.observe( ObserveConfig::default() ).await; let b = ph.observe( ObserveConfig::default() ).await; let _c = ph.observe( ObserveConfig::default() ).await; assert_eq!( ph.storage_len (), 3 ); assert_eq!( ph.num_observers(), 3 ); drop( b ); // It's important we call num_observers here, to clear the dropped one // assert_eq!( ph.storage_len (), 3 ); assert_eq!( ph.num_observers(), 2 ); assert!( ph.observers[1].is_none() ); assert_eq!( &ph.free_slots, &[1] ); let _d = ph.observe( ObserveConfig::default() ).await; assert_eq!( ph.storage_len (), 3 ); assert_eq!( ph.num_observers (), 3 ); assert_eq!( ph.free_slots.len(), 0 ); let _e = ph.observe( ObserveConfig::default() ).await; // Now we should have pushed again // assert_eq!( ph.storage_len (), 4 ); assert_eq!( ph.num_observers (), 4); assert_eq!( ph.free_slots.len(), 0 ); } // observe: verify we can no longer observe after calling close // #[async_std::test] // async fn observe_after_close() { let mut ph = Pharos::::default(); block_on( ph.close() ).expect( "close" ); let res = ph.observe( ObserveConfig::default() ).await; assert! ( res.is_err() ); assert_eq!( ErrorKind::Closed, res.unwrap_err().kind() ); } // observe: refuse Channel::Bounded(0) // #[async_std::test] // async fn observe_refuse_zero() { let mut ph = Pharos::::default(); let res = ph.observe( Channel::Bounded(0).into() ).await; assert! ( res.is_err() ); assert_eq!( ErrorKind::MinChannelSizeOne, res.unwrap_err().kind() ); } // verify that one observer blocks pharos. // #[async_std::test] // async fn poll_ready_pending() { let mut ph = Pharos::default(); let _open = ph.observe( Channel::Bounded ( 10 ).into() ).await.expect( "observe" ); let mut full = ph.observe( Channel::Bounded ( 1 ).into() ).await.expect( "observe" ); let _unbound = ph.observe( Channel::Unbounded .into() ).await.expect( "observe" ); poll_fn( move |cx| { let mut ph = Pin::new( &mut ph ); crate::assert_matches!( ph.as_mut().poll_ready( cx ), Poll::Ready( Ok(_) ) ); assert!( ph.as_mut().start_send( true ).is_ok() ); crate::assert_matches!( ph.as_mut().poll_ready( cx ), Poll::Pending ); assert_eq!( Pin::new( &mut full ).poll_next(cx), Poll::Ready(Some(true))); crate::assert_matches!( ph.as_mut().poll_ready( cx ), Poll::Ready( Ok(_) ) ); ().into() }).await; } // pharos drops closed observers. // #[async_std::test] // async fn poll_ready_drop() { let mut ph = Pharos::::default(); let _open = ph.observe( Channel::Bounded ( 10 ).into() ).await.expect( "observe" ); let full = ph.observe( Channel::Bounded ( 1 ).into() ).await.expect( "observe" ); let _unbound = ph.observe( Channel::Unbounded .into() ).await.expect( "observe" ); let mut ph = Pin::new( &mut ph ); drop( full ); poll_fn( move |cx| { crate::assert_matches!( ph.as_mut().poll_ready( cx ), Poll::Ready( Ok(_) ) ); assert!( ph.observers[1].is_none() ); ().into() }).await; } // poll_ready should return closed if the pharos is closed. // #[ test ] // fn poll_ready_closed() { block_on( poll_fn( move |cx| { let mut ph = Pharos::::default(); let mut ph = Pin::new( &mut ph ); crate::assert_matches!( ph.as_mut().poll_close( cx ), Poll::Ready(Ok(())) ); let res = ph.as_mut().poll_ready( cx ); crate::assert_matches!( res, Poll::Ready( Err(_) ) ); match res { Poll::Ready( Err( e ) ) => assert_eq!( ErrorKind::Closed, e.kind() ) , _ => unreachable!( "wrong result " ) , } ().into() })); } // start_send verify message arrives. // #[async_std::test] // async fn start_send_arrive() { let mut ph = Pharos::::default(); let _open = ph.observe( Channel::Bounded ( 10 ).into() ).await.expect( "observe" ); let mut full = ph.observe( Channel::Bounded ( 1 ).into() ).await.expect( "observe" ); let _unbound = ph.observe( Channel::Unbounded .into() ).await.expect( "observe" ); poll_fn( move |cx| { let mut ph = Pin::new( &mut ph ); crate::assert_matches!( ph.as_mut().poll_ready( cx ), Poll::Ready( Ok(_) ) ); assert!( ph.as_mut().start_send( 3 ).is_ok() ); assert_eq!( Pin::new( &mut full ).poll_next(cx), Poll::Ready(Some(3))); ().into() }).await; } // pharos drops closed observers. // #[async_std::test] // async fn poll_flush_drop() { let mut ph = Pharos::::default(); let _open = ph.observe( Channel::Bounded ( 10 ).into() ).await.expect( "observe" ); let full = ph.observe( Channel::Bounded ( 1 ).into() ).await.expect( "observe" ); let _unbound = ph.observe( Channel::Unbounded .into() ).await.expect( "observe" ); let mut ph = Pin::new( &mut ph ); drop( full ); poll_fn( move |cx| { crate::assert_matches!( ph.as_mut().poll_flush( cx ), Poll::Ready( Ok(_) ) ); assert!( ph.observers[1].is_none() ); ().into() }).await; } } pharos-0.5.3/src/shared_pharos.rs000064400000000000000000000025450072674642500151420ustar 00000000000000use crate::{ import::*, Pharos, PharErr, Observable, Observe, ObserveConfig, Events }; /// A handy wrapper that uses a futures aware mutex to allow using Pharos from a shared /// reference. // #[ derive( Debug, Clone ) ] // pub struct SharedPharos where Event: 'static + Clone + Send { pharos: Arc >>, } impl SharedPharos where Event: 'static + Clone + Send { /// Create a SharedPharos object. // pub fn new( pharos: Pharos ) -> Self { Self{ pharos: Arc::new(Mutex::new( pharos )) } } /// Notify observers. // pub async fn notify( &self, evt: Event ) -> Result<(), PharErr> { let mut ph = self.pharos.lock().await; ph.send( evt ).await } /// Start Observing this Pharos object. // pub async fn observe_shared( &self, options: ObserveConfig ) -> Result, >::Error > { let mut ph = self.pharos.lock().await; ph.observe( options ).await } } impl Default for SharedPharos where Event: 'static + Clone + Send { fn default() -> Self { Self::new( Pharos::default() ) } } impl Observable for SharedPharos where Event: 'static + Clone + Send { type Error = PharErr; fn observe( &mut self, options: ObserveConfig ) -> Observe< '_, Event, Self::Error > { self.observe_shared( options ).boxed() } }