Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update disposables docs #679

Merged
merged 4 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 2 additions & 39 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,15 +193,10 @@ See <https://reactivex.io/documentation/scheduler.html> for more details about s

### Disposable

In reactive programming, a **disposable** is an object that represents a resource that needs to be released or disposed of when it is no longer needed. This can include things like file handles, network connections, or any other resource that needs to be cleaned up after use.
\copydoc disposables

The purpose of a disposable is to provide a way to manage resources in a safe and efficient manner. By using disposables, you can ensure that resources are released in a timely manner, preventing memory leaks and other issues that can arise from resource leaks.
Check API reference of @link disposables @endlink for more details

In most cases disposables are placed in observers. RPP's observer can use two types of disposables:

1. **Upstream disposable** - This is a disposable that the observable puts into the observer. The upstream disposable keeps some state or callback that should be disposed of when the observer is disposed. This ensures that any resources used by the observable are properly cleaned up when the observer obtains on_error/on_completed or disposed in any other way.

2. **External disposable** - This is a disposable that allows the observer to be disposed of from outside the observer itself. This can be useful in situations where you need to cancel an ongoing operation or release resources before the observable has completed its work.

### Exception Guarantee

Expand Down Expand Up @@ -252,38 +247,6 @@ All disposable in RPP should be created and used via `rpp::disposable_wrapper_im
- `disposable_wrapper` - wrapper over `interface_disposable`
- `composite_disposable_wrapper` - wrapper over `interface_composite_disposable`

`disposable_wrapper` is kind of smart_pointer (like std::unique_ptr) but for disposables. So, default constructed wrapper is empty wrapper.
```cpp
auto d = rpp::disposable_wrapper{};
```
Comparing to unique_ptr wrapper's methods are safe to use for empty wrapper.
To construct wrapper you have to use `make` method:
```cpp
auto d = rpp::disposable_wrapper::make<SomeSpecificDisposableType>(some_arguments, to_construct_it);
```

Wrapper has popluar methods to work with disposable: `dispose()`, `is_disposed()` and `add()`/`remove()`/`clear()` (for `interface_composite_disposable`).

In case of you want to obtain original disposable, you can use `lock()` method returning shared_ptr.

`disposable_wrapper` can be strong and weak:
- strong (it is default behavior) is keeping disposable as shared_ptr, so, such an instance of wrapper is extending life-time is underlying disposable
- weak (disposable_wrapper can be forced to weak via `as_weak()` method) is keeping disposable as weak_ptr, so, such an instance of wrapper is **NOT** extendning life-time is underlying disposable

This wrapper is needed for 2 goals:
- provide safe usage of disposables avoiding manual handling of empty/weak disposables
- automatically call `dispose()` during destruction of any disposable

To achieve desired performance RPP is avoiding to returning disposable by default. So, it is why `subscribe` method is not returning anything by default. If you want to attach disposable to observer you can use overloading method accepting disposable as first argument like this:
```cpp
auto d = rpp::composite_disposable_wrapper::make();
observable.subscribe(d, [](int v){});
```
or use `subscribe_with_disposable` method instead
```cpp
auto d = observable.subscribe_with_disposable([](int){});
```

### dynamic_* versions to keep classes as variables

Most of the classes inside rpp library including `observable`, `observer` and others are heavy-templated classes. It means, it could has a lot of template params. In most cases you shouldn't worry about it due to it is purely internal problem.
Expand Down
23 changes: 21 additions & 2 deletions src/rpp/rpp/disposables.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,27 @@

/**
* @defgroup disposables Disposables
* @brief Disposable owns some resource and provides ability to `dispose()` it: destroy/remove/disconnect and etc.
* @details In RPP it used as "inverted subscription": observable sets disposable to observer via `set_upstream(disposable)` with meaning "if you want to cancel me -> dispose this disposable"
*
* @brief Disposable is handle/resorce passed from observable to observer via `set_upstream` method. Observer disposes this disposable when it wants to unsubscribe from observable.
victimsnino marked this conversation as resolved.
Show resolved Hide resolved
*
* @details In reactive programming, a **disposable** is an object that represents a resource that needs to be released or disposed of when it is no longer needed.
* This can include things like file handles, network connections, or any other resource that needs to be cleaned up after use.
* The purpose of a disposable is to provide a way to manage resources in a safe and efficient manner.
* By using disposables, you can ensure that resources are released in a timely manner, preventing memory leaks and other issues that can arise from resource leaks.
*
* There are 2 main purposes of disposables:
* 1. **Upstream disposable** <br>
* This is a disposable that the observable puts into the observer.
* The upstream disposable keeps some state or callback that should be disposed of when the observer is disposed (== no longer wants to receive emissions, for example, was completed/errored or just unsubscribed)
* This ensures that any resources used by the observable are properly cleaned up when the observer obtains on_error/on_completed or disposed in any other way.
*
* 2. **External disposable** <br>
* This is a disposable that allows the observer to be disposed of from outside the observer itself.
* This can be useful in situations where you need to cancel an ongoing operation or release resources before the observable has completed its work.
* To achieve this in rpp you can pass disposable to `subscribe` method or use `subscribe_with_disposable` overload instead.
*
* @note In rpp all disposables should be created via @link rpp::disposable_wrapper_impl @endlink instead of manually.
*
* @ingroup rpp
*/

Expand Down
39 changes: 35 additions & 4 deletions src/rpp/rpp/disposables/disposable_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,35 @@ namespace rpp::details
namespace rpp
{
/**
* @brief Wrapper to keep disposable. Any disposable have to be created right from this wrapper with help of `make` function.
* @details Member functions is safe to call even if internal disposable is gone. Also it provides access to "raw" shared_ptr and it can be nullptr in case of disposable empty/ptr gone.
* @details Can keep weak_ptr in case of not owning disposable
* @brief Main RPP wrapper over @link disposables @endlink.
* @details This wrapper invented to provide safe and easy-to-use access to disposables. It has next core points:
* - disposable_wrapper is kind of smart_pointer (like std::shared_ptr) but for disposables. So, default constructed wrapper is empty wrapper.
* - disposable_wrapper shares ownership like std::shared_ptr
* - disposable_wrapper's methods is safe to use over empty/gone/disposed/weak disposables.
* - as soon as disposable can be actually "any internal state" it provides access to "raw" shared_ptr and it can be nullptr in case of disposable empty/ptr gone.
* - disposable_wrapper can be strong or weak (same as std::shared_ptr). weak disposable is important, for example, when it keeps observer and this observer should keep this disposable at the same time.
* - disposable_wrapper has popluar methods to work with disposable: `dispose()`, `is_disposed()` and `add()`/`remove()`/`clear()` (for `interface_composite_disposable`).
* - any disposable created via disposable_wrapper would have call `dispose()` during it's destruction (during destruction of last disposable_wrapper owning it)
*
* To construct wrapper you have to use `make` method:
* @code{cpp}
* auto d = rpp::disposable_wrapper::make<SomeSpecificDisposableType>(some_arguments, to_construct_it);
* @endcode
*
* To achieve desired performance RPP is avoiding to returning disposable by default. So, it is why `subscribe` method is not returning anything by default. If you want to attach disposable to observer you can use overloading method accepting disposable as first argument like this:
* @code{cpp}
* auto d = rpp::composite_disposable_wrapper::make();
* observable.subscribe(d, [](int v){});
* @endcode

* or use `subscribe_with_disposable` method instead
* @code{cpp}
* auto d = observable.subscribe_with_disposable([](int){});
* @endcode
*
* @note rpp has 2 predefined disposable_wrappers for most popular cases:
* - @link rpp::disposable_wrapper @endlink is wrapper for simple @link rpp::interface_disposable @endlink
* - @link rpp::composite_disposable_wrapper @endlink is wrapper for @link rpp::composite_disposable @endlink
*
* @ingroup disposables
*/
Expand All @@ -126,7 +152,12 @@ namespace rpp
bool operator==(const disposable_wrapper_impl&) const = default;

/**
* @brief Way to create disposable_wrapper. Passed `TTarget` type can be any type derived from `TDisposable`.
* @brief Main way to create disposable_wrapper. Passed `TTarget` type can be any type derived from `TDisposable`.
* @par Example:
*
* \code{cpp}
* rpp::disposable_wrapper<rpp::interface_composite_disposable>::make<rpp::composite_disposable>();
* \endcode
*/
template<std::derived_from<TDisposable> TTarget = TDefaultMake, typename... TArgs>
requires (std::constructible_from<TTarget, TArgs && ...>)
Expand Down
Loading