├── .github ├── .gitignore └── workflows │ ├── pkgdown.yaml │ ├── R-CMD-check.yaml │ └── test-coverage.yaml ├── vignettes ├── .gitignore └── comparison-benchmarks-1.png ├── LICENSE ├── man ├── figures │ ├── logo.png │ └── parabar-design.png ├── LOGO.Rd ├── Helper.Rd ├── Warning.Rd ├── make_logo.Rd ├── Exception.Rd ├── configure_bar.Rd ├── BarFactory.Rd ├── ContextFactory.Rd ├── clear.Rd ├── BackendFactory.Rd ├── stop_backend.Rd ├── peek.Rd ├── evaluate.Rd ├── export.Rd ├── option.Rd ├── par_lapply.Rd ├── par_sapply.Rd ├── Bar.Rd ├── Specification.Rd ├── BasicBar.Rd ├── ModernBar.Rd ├── par_apply.Rd ├── Backend.Rd ├── Options.Rd ├── start_backend.Rd ├── SessionState.Rd └── TaskState.Rd ├── pkgdown ├── favicon │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ └── apple-touch-icon-180x180.png └── extra.scss ├── .gitignore ├── tests ├── testthat │ ├── test-package-setup.R │ ├── test-bar-factory.R │ ├── test-backend-factory.R │ ├── test-context-factory.R │ ├── test-service.R │ ├── test-bar.R │ ├── test-backend.R │ ├── test-logo.R │ ├── test-options.R │ ├── test-sync-backend.R │ ├── test-context.R │ ├── test-backend-finalizer.R │ ├── test-states.R │ ├── test-async-backend.R │ └── test-specification.R └── testthat.R ├── .Rbuildignore ├── parabar.Rproj ├── man-roxygen ├── make-logo.R ├── get-option.R ├── set-default-options.R ├── clear.R ├── stop-backend.R ├── evaluate.R ├── peek.R ├── export.R ├── set-option.R ├── configure-bar.R ├── par-lapply.R ├── par-sapply.R ├── par-apply.R └── start-backend.R ├── inst └── assets │ └── logo │ └── parabar-logo.txt ├── LICENSE.md ├── _pkgdown.yml ├── NAMESPACE ├── DESCRIPTION └── R ├── parabar-package.R ├── BarFactory.R ├── ContextFactory.R ├── BackendFactory.R ├── logo.R ├── Bar.R ├── Backend.R ├── Warning.R ├── Helper.R ├── BasicBar.R ├── ModernBar.R ├── SessionState.R ├── exports.R └── TaskState.R /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2022 2 | COPYRIGHT HOLDER: Mihai A. Constantin 3 | -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaiconstantin/parabar/HEAD/man/figures/logo.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaiconstantin/parabar/HEAD/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /man/figures/parabar-design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaiconstantin/parabar/HEAD/man/figures/parabar-design.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaiconstantin/parabar/HEAD/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaiconstantin/parabar/HEAD/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaiconstantin/parabar/HEAD/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /vignettes/comparison-benchmarks-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaiconstantin/parabar/HEAD/vignettes/comparison-benchmarks-1.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaiconstantin/parabar/HEAD/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaiconstantin/parabar/HEAD/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaiconstantin/parabar/HEAD/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaiconstantin/parabar/HEAD/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihaiconstantin/parabar/HEAD/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .DS_Store 4 | .vscode/ 5 | parabar.Rcheck/ 6 | *.tar.gz 7 | **/temp* 8 | .temp 9 | .cache 10 | docs 11 | inst/doc 12 | /doc/ 13 | /Meta/ 14 | -------------------------------------------------------------------------------- /tests/testthat/test-package-setup.R: -------------------------------------------------------------------------------- 1 | # Test `parabar` package setup. 2 | 3 | test_that("'parabar' package attaches correctly", { 4 | # Expect the package to be in the search path. 5 | expect_true("package:parabar" %in% search()) 6 | 7 | # Expect the package options to be in the session after loading. 8 | expect_true(is(getOption("parabar"), "Options")) 9 | }) 10 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE.md$ 4 | ^\.vscode$ 5 | ^inst/debug$ 6 | ^inst/design$ 7 | ^archive$ 8 | ^\.github$ 9 | ^man-roxygen$ 10 | ^CNAME$ 11 | ^temp.*$ 12 | ^docs$ 13 | ^package\.json$ 14 | ^package-lock\.json$ 15 | ^yarn\.lock$ 16 | ^node_modules$ 17 | ^_pkgdown\.yml$ 18 | ^pkgdown$ 19 | ^vignettes/articles$ 20 | ^doc$ 21 | ^Meta$ 22 | ^vignettes/.*\.Rmd\.orig$ 23 | -------------------------------------------------------------------------------- /parabar.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 4 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | LineEndingConversion: Posix 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /man/LOGO.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logo.R 3 | \docType{data} 4 | \name{LOGO} 5 | \alias{LOGO} 6 | \title{The Package Logo} 7 | \format{ 8 | An object of class \code{character} containing the \code{ASCII} logo. 9 | } 10 | \usage{ 11 | LOGO 12 | } 13 | \description{ 14 | The logo is generated by \code{\link[=make_logo]{make_logo()}} and displayed on package 15 | attach for interactive \code{R} sessions. 16 | } 17 | \examples{ 18 | print(LOGO) 19 | 20 | } 21 | \seealso{ 22 | \code{\link[=make_logo]{make_logo()}} 23 | } 24 | \keyword{datasets} 25 | -------------------------------------------------------------------------------- /tests/testthat/test-bar-factory.R: -------------------------------------------------------------------------------- 1 | # Test `BarFactory` class. 2 | 3 | test_that("'BarFactory' produces the correct instance types", { 4 | # Create a bar factory. 5 | bar_factory <- BarFactory$new() 6 | 7 | # Expect a basic bar. 8 | expect_equal( 9 | Helper$get_class_name(bar_factory$get("basic")), 10 | "BasicBar" 11 | ) 12 | 13 | # Expect a modern bar. 14 | expect_equal( 15 | Helper$get_class_name(bar_factory$get("modern")), 16 | "ModernBar" 17 | ) 18 | 19 | # Expect error for unsupported bar types. 20 | expect_error( 21 | bar_factory$get("unsupported"), 22 | as_text(Exception$feature_not_developed()) 23 | ) 24 | }) 25 | -------------------------------------------------------------------------------- /man-roxygen/make-logo.R: -------------------------------------------------------------------------------- 1 | #' @title 2 | #' Generate Package Logo 3 | #' 4 | #' @description 5 | #' This function is meant for generating or updating the logo. After running 6 | #' this procedure we end up with what is stored in the [`parabar::LOGO`] 7 | #' constant. 8 | #' 9 | #' @param template A character string representing the path to the logo 10 | #' template. 11 | #' 12 | #' @param version A numerical vector of three positive integers representing the 13 | #' version of the package to append to the logo. 14 | #' 15 | #' @return The ASCII logo. 16 | #' 17 | #' @examples 18 | #' \dontrun{ 19 | #' 20 | #' # Generate the logo. 21 | #' logo <- make_logo() 22 | #' 23 | #' # Print the logo. 24 | #' cat(logo) 25 | #' } 26 | #' 27 | #' @seealso [`parabar::LOGO`] 28 | -------------------------------------------------------------------------------- /tests/testthat/test-backend-factory.R: -------------------------------------------------------------------------------- 1 | # Test `BackendFactory` class. 2 | 3 | test_that("'BackendFactory' produces the correct instance types", { 4 | # Create a backend factory. 5 | backend_factory <- BackendFactory$new() 6 | 7 | # Expect a synchronous backend. 8 | expect_equal( 9 | Helper$get_class_name(backend_factory$get("sync")), 10 | "SyncBackend" 11 | ) 12 | 13 | # Expect an asynchronous backend. 14 | expect_equal( 15 | Helper$get_class_name(backend_factory$get("async")), 16 | "AsyncBackend" 17 | ) 18 | 19 | # Expect error for unsupported backend types. 20 | expect_error( 21 | backend_factory$get("unsupported"), 22 | as_text(Exception$feature_not_developed()) 23 | ) 24 | }) 25 | -------------------------------------------------------------------------------- /man-roxygen/get-option.R: -------------------------------------------------------------------------------- 1 | #' @description 2 | #' The [parabar::get_option()] function is a helper for retrieving the value of 3 | #' [`parabar::parabar`] [`options`][base::options()]. If the 4 | #' [`option`][`parabar::Options`] requested is not available in the session 5 | #' [`base::.Options`] list, the corresponding default value set by the 6 | #' [`parabar::Options`] [`R6::R6`] class is returned instead. 7 | #' 8 | #' @return 9 | #' The [parabar::get_option()] function returns the value of the requested 10 | #' [`option`][`parabar::Options`] present in the [`base::.Options`] list, or its 11 | #' corresponding default value (i.e., see [`parabar::Options`]). If the 12 | #' requested [`option`][`parabar::Options`] is not known, an error is thrown. 13 | #' 14 | #' @rdname option 15 | -------------------------------------------------------------------------------- /tests/testthat/test-context-factory.R: -------------------------------------------------------------------------------- 1 | # Test `ContextFactory` class. 2 | 3 | test_that("'ContextFactory' produces the correct instance types", { 4 | # Create a context factory. 5 | context_factory <- ContextFactory$new() 6 | 7 | # Expect a regular context. 8 | expect_equal( 9 | Helper$get_class_name(context_factory$get("regular")), 10 | "Context" 11 | ) 12 | 13 | # Expect a progress tracking context. 14 | expect_equal( 15 | Helper$get_class_name(context_factory$get("progress")), 16 | "ProgressTrackingContext" 17 | ) 18 | 19 | # Expect error for unsupported context types. 20 | expect_error( 21 | context_factory$get("unsupported"), 22 | as_text(Exception$feature_not_developed()) 23 | ) 24 | }) 25 | -------------------------------------------------------------------------------- /man-roxygen/set-default-options.R: -------------------------------------------------------------------------------- 1 | #' @description 2 | #' The [parabar::set_default_options()] function is used to set the default 3 | #' [`options`][base::options()] values for the [`parabar::parabar`] package. The 4 | #' function is automatically called at package load and the entry created can be 5 | #' retrieved via [`getOption("parabar")`][base::getOption()]. Specific package 6 | #' [`options`][base::options()] can be retrieved using the helper function 7 | #' [parabar::get_option()]. 8 | #' 9 | #' @return 10 | #' The [parabar::set_default_options()] function returns void. The 11 | #' [`options`][base::options()] set can be consulted via the [`base::.Options`] 12 | #' list. See the [`parabar::Options`] [`R6::R6`] class for more information on 13 | #' the default values set by this function. 14 | #' 15 | #' @rdname option 16 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/tests.html 7 | # * https://testthat.r-lib.org/reference/test_package.html#special-files 8 | 9 | # Set environmental variable to prevent hang on quitting the `R` session. 10 | # Error message: 11 | # - `Error while shutting down parallel: unable to terminate some child processes` 12 | # See: 13 | # - https://github.com/r-lib/processx/issues/310 14 | # - https://github.com/r-lib/processx/issues/240 15 | # - https://github.com/r-lib/callr/issues/158 16 | Sys.setenv(PROCESSX_NOTIFY_OLD_SIGCHLD = "true") 17 | 18 | library(testthat) 19 | library(parabar) 20 | 21 | test_check("parabar") 22 | -------------------------------------------------------------------------------- /man/Helper.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/Helper.R 3 | \name{Helper} 4 | \alias{Helper} 5 | \title{Package Helpers} 6 | \format{ 7 | \describe{ 8 | \item{\code{Helper$get_class_name(object)}}{Helper for getting the class of a given object.} 9 | \item{\code{Helper$is_of_class(object, class)}}{Check if an object is of a certain class.} 10 | \item{\code{Helper$get_option(option)}}{Get package option, or corresponding default value.} 11 | \item{\code{Helper$set_option(option, value)}}{Set package option.} 12 | \item{\code{Helper$check_object_type(object, expected_type)}}{Check the type of a given object.} 13 | \item{\code{Helper$check_array_margins(margins, dimensions)}}{Helper to check array margins for the \code{BackendService$apply} operation.} 14 | } 15 | } 16 | \description{ 17 | This class contains static helper methods. 18 | } 19 | 20 | -------------------------------------------------------------------------------- /inst/assets/logo/parabar-logo.txt: -------------------------------------------------------------------------------- 1 | . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 | . _ v{{major}}.{{minor}}.{{patch}} . 3 | . | | . 4 | . _ __ __ _ _ __ __ _ | |__ __ _ _ __ . 5 | . | '_ \ / _` || '__| / _` || '_ \ / _` || '__| . 6 | . | |_) || (_| || | | (_| || |_) || (_| || | . 7 | . | .__/ \____||_| \____||____/ \____||_| . 8 | . | | . 9 | . |_| . 10 | . . 11 | . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 | . . . 13 | . https://parabar.mihaiconstantin.com . 14 | . . . 15 | . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 | -------------------------------------------------------------------------------- /tests/testthat/test-service.R: -------------------------------------------------------------------------------- 1 | # Test `BackendService` interface. 2 | 3 | test_that("'BackendService' interface cannot be instantiated", { 4 | # Create a mock object that has the `BackendService` class. 5 | assign("object", "Mock Backend Service", envir = environment()) 6 | 7 | # Assign a class to the mock service object. 8 | class(object) <- "BackendService" 9 | 10 | # Expect an error upon instantiation. 11 | expect_error( 12 | BackendService$new(), 13 | as_text(Exception$abstract_class_not_instantiable(object)) 14 | ) 15 | }) 16 | 17 | 18 | test_that("'BackendService' interface methods throw errors", { 19 | # Create an improper backend service implementation. 20 | service <- BackendServiceImplementation$new() 21 | 22 | # Expect unimplemented backend service methods to throw errors. 23 | tests_set_for_unimplemented_service_methods(service) 24 | }) 25 | -------------------------------------------------------------------------------- /man/Warning.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/Warning.R 3 | \name{Warning} 4 | \alias{Warning} 5 | \title{Package Warnings} 6 | \format{ 7 | \describe{ 8 | \item{\code{Warning$requested_cluster_cores_too_low()}}{Warning for not requesting enough cluster cores.} 9 | \item{\code{Warning$requested_cluster_cores_too_high()}}{Warning for requesting too many cluster cores.} 10 | \item{\code{Warning$requested_cluster_type_not_supported()}}{Warning for requesting an unsupported cluster type.} 11 | \item{\code{Warning$progress_not_supported_for_backend()}}{Warning for using a backend incompatible with progress tracking.} 12 | \item{\code{Warning$error_in_backend_finalizer()}}{Warning for errors in the backend finalizer during garbage collection.} 13 | } 14 | } 15 | \description{ 16 | This class contains static methods for throwing warnings with informative 17 | messages. 18 | } 19 | 20 | -------------------------------------------------------------------------------- /man/make_logo.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logo.R 3 | \name{make_logo} 4 | \alias{make_logo} 5 | \title{Generate Package Logo} 6 | \usage{ 7 | make_logo( 8 | template = "./inst/assets/logo/parabar-logo.txt", 9 | version = c(1, 0, 0) 10 | ) 11 | } 12 | \arguments{ 13 | \item{template}{A character string representing the path to the logo 14 | template.} 15 | 16 | \item{version}{A numerical vector of three positive integers representing the 17 | version of the package to append to the logo.} 18 | } 19 | \value{ 20 | The ASCII logo. 21 | } 22 | \description{ 23 | This function is meant for generating or updating the logo. After running 24 | this procedure we end up with what is stored in the \code{\link{LOGO}} 25 | constant. 26 | } 27 | \examples{ 28 | \dontrun{ 29 | 30 | # Generate the logo. 31 | logo <- make_logo() 32 | 33 | # Print the logo. 34 | cat(logo) 35 | } 36 | 37 | } 38 | \seealso{ 39 | \code{\link{LOGO}} 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2022 Mihai A. Constantin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /man-roxygen/clear.R: -------------------------------------------------------------------------------- 1 | #' @title 2 | #' Clear a Backend 3 | #' 4 | #' @description 5 | #' This function can be used to clear a [`backend`][`parabar::Backend`] created 6 | #' by [parabar::start_backend()]. 7 | #' 8 | #' @param backend An object of class [`parabar::Backend`] as returned by the 9 | #' [parabar::start_backend()] function. 10 | #' 11 | #' @details 12 | #' This function is a convenience wrapper around the lower-lever API of 13 | #' [`parabar::parabar`] aimed at developers. More specifically, this function 14 | #' calls the [`clear`][`parabar::BackendService`] method on the provided 15 | #' [`backend`][`parabar::Backend`] instance. 16 | #' 17 | #' @return 18 | #' The function returns void. It throws an error if the value provided for the 19 | #' `backend` argument is not an instance of class [`parabar::Backend`]. 20 | #' 21 | #' @inherit start_backend examples 22 | #' 23 | #' @seealso 24 | #' [parabar::start_backend()], [parabar::peek()], [parabar::export()], 25 | #' [parabar::evaluate()], [parabar::configure_bar()], [parabar::par_sapply()], 26 | #' [parabar::par_lapply()], [parabar::par_apply()], [parabar::stop_backend()], 27 | #' and [`parabar::BackendService`]. 28 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: http://parabar.mihaiconstantin.com/ 2 | template: 3 | bootstrap: 5 4 | bootswatch: flatly 5 | reference: 6 | - subtitle: Main Functions 7 | desc: Functions intended for use in interactive `R` sessions. 8 | - contents: 9 | - start_backend 10 | - stop_backend 11 | - export 12 | - peek 13 | - clear 14 | - evaluate 15 | - par_sapply 16 | - par_lapply 17 | - par_apply 18 | - configure_bar 19 | - matches("get_|set_") 20 | - subtitle: Developer Classes 21 | desc: API based on the `R6` class system for package developers. 22 | - contents: 23 | - BackendService 24 | - Backend 25 | - SyncBackend 26 | - AsyncBackend 27 | - BackendFactory 28 | - Specification 29 | - Context 30 | - ProgressTrackingContext 31 | - ContextFactory 32 | - SessionState 33 | - TaskState 34 | - Bar 35 | - BasicBar 36 | - ModernBar 37 | - BarFactory 38 | - Options 39 | - Helper 40 | - Warning 41 | - Exception 42 | - UserApiConsumer 43 | - subtitle: Miscellaneous 44 | desc: Very specific functions that may safely be ignored. 45 | - contents: 46 | - make_logo 47 | - LOGO 48 | authors: 49 | Mihai Constantin: 50 | href: "https://mihaiconstantin.com" 51 | -------------------------------------------------------------------------------- /man-roxygen/stop-backend.R: -------------------------------------------------------------------------------- 1 | #' @title 2 | #' Stop a Backend 3 | #' 4 | #' @description 5 | #' This function can be used to stop a [`backend`][`parabar::Backend`] created 6 | #' by [parabar::start_backend()]. 7 | #' 8 | #' @param backend An object of class [`parabar::Backend`] as returned by the 9 | #' [parabar::start_backend()] function. 10 | #' 11 | #' @details 12 | #' This function is a convenience wrapper around the lower-lever API of 13 | #' [`parabar::parabar`] aimed at developers. More specifically, this function 14 | #' calls the [`stop`][`parabar::BackendService`] method on the provided 15 | #' [`backend`][`parabar::Backend`] instance. 16 | #' 17 | #' @return 18 | #' The function returns void. It throws an error if: 19 | #' - the value provided for the `backend` argument is not an instance of class 20 | #' [`parabar::Backend`]. 21 | #' - the [`backend`][`parabar::Backend`] object provided is already stopped 22 | #' (i.e., is not active). 23 | #' 24 | #' @inherit start_backend examples 25 | #' 26 | #' @seealso 27 | #' [parabar::start_backend()], [parabar::peek()], [parabar::export()], 28 | #' [parabar::evaluate()], [parabar::clear()], [parabar::configure_bar()], 29 | #' [parabar::par_sapply()], [parabar::par_apply()], [parabar::par_lapply()], and 30 | #' [`parabar::BackendService`]. 31 | -------------------------------------------------------------------------------- /man-roxygen/evaluate.R: -------------------------------------------------------------------------------- 1 | #' @title 2 | #' Evaluate An Expression On The Backend 3 | #' 4 | #' @description 5 | #' This function can be used to evaluate an arbitrary [base::expression()] a 6 | #' [`backend`][`parabar::Backend`] created by [parabar::start_backend()]. 7 | #' 8 | #' @param backend An object of class [`parabar::Backend`] as returned by the 9 | #' [parabar::start_backend()] function. 10 | #' 11 | #' @param expression An unquoted expression to evaluate on the backend. 12 | #' 13 | #' @details 14 | #' This function is a convenience wrapper around the lower-lever API of 15 | #' [`parabar::parabar`] aimed at developers. More specifically, this function 16 | #' calls the [`evaluate`][`parabar::BackendService`] method on the provided 17 | #' [`backend`][`parabar::Backend`] instance. 18 | #' 19 | #' @return 20 | #' This method returns the result of the expression evaluation. It throws an 21 | #' error if the value provided for the `backend` argument is not an instance of 22 | #' class [`parabar::Backend`]. 23 | #' 24 | #' @inherit start_backend examples 25 | #' 26 | #' @seealso 27 | #' [parabar::start_backend()], [parabar::peek()], [parabar::export()], 28 | #' [parabar::clear()], [parabar::configure_bar()], [parabar::par_sapply()], 29 | #' [parabar::par_lapply()], [parabar::par_apply()], [parabar::stop_backend()], 30 | #' and [`parabar::BackendService`]. 31 | -------------------------------------------------------------------------------- /tests/testthat/test-bar.R: -------------------------------------------------------------------------------- 1 | # Test `Bar` class. 2 | 3 | test_that("'Bar' class cannot be instantiated", { 4 | # Create a mock object that has the `Service` class. 5 | assign("object", "Mock Bar", envir = environment()) 6 | 7 | # Assign a class to the mock service object. 8 | class(object) <- "Bar" 9 | 10 | # Expect an error upon instantiation. 11 | expect_error(Bar$new(), as_text(Exception$abstract_class_not_instantiable(object))) 12 | }) 13 | 14 | 15 | test_that("'Bar' abstract class methods throw errors", { 16 | # Create an improper bar implementation. 17 | bar <- BarImplementation$new() 18 | 19 | # Expect an error when calling the `create` method. 20 | expect_error(bar$create(), as_text(Exception$method_not_implemented())) 21 | 22 | # Expect an error when calling the `update` method. 23 | expect_error(bar$update(), as_text(Exception$method_not_implemented())) 24 | 25 | # Expect an error when calling the `terminate` method. 26 | expect_error(bar$terminate(), as_text(Exception$method_not_implemented())) 27 | }) 28 | 29 | 30 | test_that("'Bar' abstract class fields have correct default values", { 31 | # Create an improper bar implementation. 32 | bar <- BarImplementation$new() 33 | 34 | # Expect the correct default value for the `engine` field. 35 | expect_null(bar$engine) 36 | }) 37 | -------------------------------------------------------------------------------- /man-roxygen/peek.R: -------------------------------------------------------------------------------- 1 | #' @title 2 | #' Inspect a Backend 3 | #' 4 | #' @description 5 | #' This function can be used to check the names of the variables present on a 6 | #' [`backend`][`parabar::Backend`] created by [parabar::start_backend()]. 7 | #' 8 | #' @param backend An object of class [`parabar::Backend`] as returned by the 9 | #' [parabar::start_backend()] function. 10 | #' 11 | #' @details 12 | #' This function is a convenience wrapper around the lower-lever API of 13 | #' [`parabar::parabar`] aimed at developers. More specifically, this function 14 | #' calls the [`peek`][`parabar::BackendService`] method on the provided 15 | #' [`backend`][`parabar::Backend`] instance. 16 | #' 17 | #' @return 18 | #' The function returns a list of character vectors, where each list element 19 | #' corresponds to a node, and each element of the character vector is the name 20 | #' of a variable present on that node. It throws an error if the value provided 21 | #' for the `backend` argument is not an instance of class [`parabar::Backend`]. 22 | #' 23 | #' @inherit start_backend examples 24 | #' 25 | #' @seealso 26 | #' [parabar::start_backend()], [parabar::export()], [parabar::evaluate()], 27 | #' [parabar::clear()], [parabar::configure_bar()], [parabar::par_sapply()], 28 | #' [parabar::par_lapply()], [parabar::par_apply()], [parabar::stop_backend()], 29 | #' and [`parabar::BackendService`]. 30 | -------------------------------------------------------------------------------- /tests/testthat/test-backend.R: -------------------------------------------------------------------------------- 1 | # Test `Backend` class. 2 | 3 | test_that("'Backend' class cannot be instantiated", { 4 | # Create a mock object that has the `Service` class. 5 | assign("object", "Mock Backend", envir = environment()) 6 | 7 | # Assign a class to the mock service object. 8 | class(object) <- "Backend" 9 | 10 | # Expect an error upon instantiation. 11 | expect_error( 12 | Backend$new(), 13 | as_text(Exception$abstract_class_not_instantiable(object)) 14 | ) 15 | }) 16 | 17 | 18 | test_that("'Backend' abstract class methods throw errors", { 19 | # Create an improper backend implementation. 20 | backend <- BackendImplementation$new() 21 | 22 | # Expect unimplemented service methods to throw errors. 23 | tests_set_for_unimplemented_service_methods(backend) 24 | }) 25 | 26 | 27 | test_that("'Backend' abstract class fields have correct default values", { 28 | # Create an improper backend implementation. 29 | backend <- BackendImplementation$new() 30 | 31 | # Expect the correct default value for the `cluster` field. 32 | expect_null(backend$cluster) 33 | 34 | # Expect the correct default value for the `supports_progress` field. 35 | expect_false(backend$supports_progress) 36 | 37 | # Expect the correct default value for the `active` field. 38 | expect_false(backend$active) 39 | }) 40 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(print,parabar) 4 | export(AsyncBackend) 5 | export(Backend) 6 | export(BackendFactory) 7 | export(BackendService) 8 | export(Bar) 9 | export(BarFactory) 10 | export(BasicBar) 11 | export(Context) 12 | export(ContextFactory) 13 | export(Exception) 14 | export(Helper) 15 | export(LOGO) 16 | export(ModernBar) 17 | export(Options) 18 | export(ProgressTrackingContext) 19 | export(SessionState) 20 | export(Specification) 21 | export(SyncBackend) 22 | export(TaskState) 23 | export(UserApiConsumer) 24 | export(Warning) 25 | export(clear) 26 | export(configure_bar) 27 | export(evaluate) 28 | export(export) 29 | export(get_option) 30 | export(make_logo) 31 | export(par_apply) 32 | export(par_lapply) 33 | export(par_sapply) 34 | export(peek) 35 | export(set_default_options) 36 | export(set_option) 37 | export(start_backend) 38 | export(stop_backend) 39 | importFrom(R6,R6Class) 40 | importFrom(callr,r_session) 41 | importFrom(filelock,lock) 42 | importFrom(filelock,unlock) 43 | importFrom(parallel,clusterCall) 44 | importFrom(parallel,clusterEvalQ) 45 | importFrom(parallel,clusterExport) 46 | importFrom(parallel,detectCores) 47 | importFrom(parallel,makeCluster) 48 | importFrom(parallel,parApply) 49 | importFrom(parallel,parLapply) 50 | importFrom(parallel,parSapply) 51 | importFrom(parallel,stopCluster) 52 | importFrom(progress,progress_bar) 53 | -------------------------------------------------------------------------------- /man-roxygen/export.R: -------------------------------------------------------------------------------- 1 | #' @title 2 | #' Export Objects To a Backend 3 | #' 4 | #' @description 5 | #' This function can be used to export objects to a 6 | #' [`backend`][`parabar::Backend`] created by [parabar::start_backend()]. 7 | #' 8 | #' @param backend An object of class [`parabar::Backend`] as returned by the 9 | #' [parabar::start_backend()] function. 10 | #' 11 | #' @param variables A character vector of variable names to export to the 12 | #' backend. 13 | #' 14 | #' @param environment An environment from which to export the variables. If no 15 | #' environment is provided, the `.GlobalEnv` environment is used. 16 | #' 17 | #' @details 18 | #' This function is a convenience wrapper around the lower-lever API of 19 | #' [`parabar::parabar`] aimed at developers. More specifically, this function 20 | #' calls the [`export`][`parabar::BackendService`] method on the provided 21 | #' [`backend`][`parabar::Backend`] instance. 22 | #' 23 | #' @return 24 | #' The function returns void. It throws an error if the value provided for the 25 | #' `backend` argument is not an instance of class [`parabar::Backend`]. 26 | #' 27 | #' @inherit start_backend examples 28 | #' 29 | #' @seealso 30 | #' [parabar::start_backend()], [parabar::peek()], [parabar::evaluate()], 31 | #' [parabar::clear()], [parabar::configure_bar()], [parabar::par_sapply()], 32 | #' [parabar::par_lapply()], [parabar::par_apply()], [parabar::stop_backend()], 33 | #' and [`parabar::BackendService`]. 34 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: parabar 2 | Title: Progress Bar for Parallel Tasks 3 | Version: 1.4.2 4 | Authors@R: 5 | person(given = "Mihai", 6 | family = "Constantin", 7 | role = c("aut", "cre"), 8 | email = "mihai@mihaiconstantin.com", 9 | comment = c(ORCID = "0000-0002-6460-0107")) 10 | Description: A simple interface in the form of R6 classes for executing tasks in 11 | parallel, tracking their progress, and displaying accurate progress bars. 12 | License: MIT + file LICENSE 13 | URL: https://parabar.mihaiconstantin.com 14 | BugReports: https://github.com/mihaiconstantin/parabar/issues 15 | Imports: 16 | R6, 17 | progress, 18 | parallel, 19 | callr, 20 | filelock, 21 | utils 22 | Encoding: UTF-8 23 | Roxygen: list(markdown = TRUE, r6 = TRUE) 24 | RoxygenNote: 7.3.1 25 | Collate: 26 | 'TaskState.R' 27 | 'Options.R' 28 | 'Helper.R' 29 | 'Exception.R' 30 | 'Specification.R' 31 | 'BackendService.R' 32 | 'Backend.R' 33 | 'AsyncBackend.R' 34 | 'SyncBackend.R' 35 | 'BackendFactory.R' 36 | 'Bar.R' 37 | 'ModernBar.R' 38 | 'BasicBar.R' 39 | 'BarFactory.R' 40 | 'Context.R' 41 | 'ProgressTrackingContext.R' 42 | 'ContextFactory.R' 43 | 'SessionState.R' 44 | 'Warning.R' 45 | 'UserApiConsumer.R' 46 | 'exports.R' 47 | 'logo.R' 48 | 'parabar-package.R' 49 | Suggests: 50 | knitr, 51 | rmarkdown, 52 | testthat (>= 3.0.0) 53 | Config/testthat/edition: 3 54 | VignetteBuilder: knitr 55 | -------------------------------------------------------------------------------- /tests/testthat/test-logo.R: -------------------------------------------------------------------------------- 1 | # Test for logo-related objects. 2 | 3 | test_that("'make_logo' function correctly sets the package version", { 4 | # Create a temporary file. 5 | path <- tempfile("parabar-logo") 6 | 7 | # Create a mock logo template. 8 | mock_logo_template <- c("parabar {{major}}.{{minor}}.{{patch}}") 9 | 10 | # Write mock logo template to the temporary file. 11 | writeLines(mock_logo_template, path) 12 | 13 | # Remove the file on exit. 14 | on.exit({ 15 | # Remove. 16 | unlink(path) 17 | }) 18 | 19 | # Expect that the logo is produced with the correct version. 20 | expect_equal( 21 | make_logo(template = path, version = c(1, 2, 3)), 22 | c("parabar 1.2.3") 23 | ) 24 | 25 | # Expect that the logo version can also handle character. 26 | expect_equal( 27 | make_logo(template = path, version = c(1, "x", "x")), 28 | c("parabar 1.x.x") 29 | ) 30 | }) 31 | 32 | 33 | test_that("'LOGO' constant exists and is of correct type", { 34 | # Expect that the `LOGO` constant is not null. 35 | expect_false(is.null(LOGO)) 36 | 37 | # Expect that the `LOGO` constant has the correct type. 38 | expect_true(is(LOGO, "parabar")) 39 | }) 40 | 41 | 42 | test_that("'LOGO' is printed correctly", { 43 | # Capture the output of the `S3` method. 44 | output <- capture.output(print(LOGO)) 45 | 46 | # Expect the logo to contain a given package version. 47 | expect_true(grepl("v\\d\\.x\\.x", output[2], perl = TRUE)) 48 | }) 49 | -------------------------------------------------------------------------------- /man-roxygen/set-option.R: -------------------------------------------------------------------------------- 1 | #' @title 2 | #' Get or Set Package Option 3 | #' 4 | #' @description 5 | #' The [parabar::set_option()] function is a helper for setting 6 | #' [`parabar::parabar`] [`options`][base::options()]. The function adjusts the 7 | #' fields of the [`parabar::Options`] instance stored in the [`base::.Options`] 8 | #' list. If no [`parabar::Options`] instance is present in the 9 | #' [`base::.Options`] list, a new one is created. 10 | #' 11 | #' @param option A character string representing the name of the option to 12 | #' retrieve or adjust. See the public fields of [`R6::R6`] class 13 | #' [`parabar::Options`] for the list of available [`parabar::parabar`] 14 | #' [`options`][base::options()]. 15 | #' 16 | #' @param value The value to set the [`option`][`parabar::Options`] to. 17 | #' 18 | #' @return 19 | #' The [parabar::set_option()] function returns void. It throws an error if the 20 | #' requested [`option`][`parabar::Options`] to be adjusted is not known. 21 | #' 22 | #' @examples 23 | #' # Get the status of progress tracking. 24 | #' get_option("progress_track") 25 | #' 26 | #' # Set the status of progress tracking to `FALSE`. 27 | #' set_option("progress_track", FALSE) 28 | #' 29 | #' # Get the status of progress tracking again. 30 | #' get_option("progress_track") 31 | #' 32 | #' # Restore default options. 33 | #' set_default_options() 34 | #' 35 | #' # Get the status of progress tracking yet again. 36 | #' get_option("progress_track") 37 | #' 38 | #' @seealso 39 | #' [`parabar::Options`], [parabar::set_default_options()], [base::options()], 40 | #' and [base::getOption()]. 41 | #' 42 | #' @rdname option 43 | -------------------------------------------------------------------------------- /R/parabar-package.R: -------------------------------------------------------------------------------- 1 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # 2 | # _ # 3 | # | | # 4 | # _ __ __ _ _ __ __ _ | |__ __ _ _ __ # 5 | # | '_ \ / _` || '__| / _` || '_ \ / _` || '__| # 6 | # | |_) || (_| || | | (_| || |_) || (_| || | # 7 | # | .__/ \____||_| \____||____/ \____||_| # 8 | # | | # 9 | # |_| # 10 | # # 11 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # 12 | # Author: Mihai A. Constantin # 13 | # Documentation: https://parabar.mihaiconstantin.com # 14 | # Contact: mihai@mihaiconstantin.com # 15 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # 16 | 17 | 18 | # Imports. 19 | #' @importFrom parallel detectCores makeCluster stopCluster clusterExport 20 | #' @importFrom parallel clusterEvalQ parSapply parLapply parApply clusterCall 21 | #' @importFrom R6 R6Class 22 | #' @importFrom progress progress_bar 23 | #' @importFrom callr r_session 24 | #' @importFrom filelock lock unlock 25 | 26 | #' @template parabar 27 | "_PACKAGE" 28 | 29 | 30 | # On package load. 31 | .onLoad <- function(libname, pkgname) { 32 | # Set package options. 33 | set_default_options() 34 | } 35 | 36 | 37 | # On package attach. 38 | .onAttach <- function(libname, pkgname) { 39 | # If there this is an interactive session. 40 | if (interactive()) { 41 | # Print package information. 42 | packageStartupMessage(LOGO) 43 | } 44 | } 45 | 46 | 47 | # On package unload. 48 | .onUnload <- function(libpath) { 49 | # Remove package options. 50 | options(parabar = NULL) 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # `pkgdown` workflow. 2 | name: pkgdown 3 | 4 | # Set workflow permissions. 5 | permissions: read-all 6 | 7 | # Events. 8 | on: 9 | push: 10 | branches: 11 | - main 12 | pull_request: 13 | branches: 14 | - main 15 | release: 16 | types: [published] 17 | # Trigger manually via the GitHub UI. 18 | workflow_dispatch: 19 | 20 | # Jobs to run. 21 | jobs: 22 | 23 | # `pkgdown` job. 24 | pkgdown: 25 | runs-on: ubuntu-latest 26 | concurrency: 27 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 28 | env: 29 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 30 | permissions: 31 | contents: write 32 | 33 | # Actions. 34 | steps: 35 | # Checkout. 36 | - name: Checkout repository 37 | uses: actions/checkout@v4 38 | 39 | # Setup `pandoc`. 40 | - name: Setup pandoc 41 | uses: r-lib/actions/setup-pandoc@v2 42 | 43 | # Setup `R`. 44 | - name: Setup R 45 | uses: r-lib/actions/setup-r@v2 46 | with: 47 | use-public-rspm: true 48 | 49 | # Setup package dependencies and extras. 50 | - name: Install dependencies 51 | uses: r-lib/actions/setup-r-dependencies@v2 52 | with: 53 | extra-packages: any::pkgdown, local::. 54 | needs: website 55 | 56 | # Build the site. 57 | - name: Build site 58 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 59 | shell: Rscript {0} 60 | 61 | # Deploy to GitHub pages. 62 | - name: Deploy to GitHub pages 🚀 63 | if: github.event_name != 'pull_request' 64 | uses: JamesIves/github-pages-deploy-action@v4.5.0 65 | with: 66 | clean: false 67 | branch: gh-pages 68 | folder: docs 69 | -------------------------------------------------------------------------------- /R/BarFactory.R: -------------------------------------------------------------------------------- 1 | #' @include Exception.R BasicBar.R ModernBar.R 2 | 3 | #' @title 4 | #' BackendFactory 5 | #' 6 | #' @description 7 | #' This class is a factory that provides concrete implementations of the 8 | #' [`parabar::Bar`] abstract class. 9 | #' 10 | #' @examples 11 | #' # Create a bar factory. 12 | #' bar_factory <- BarFactory$new() 13 | #' 14 | #' # Get a modern bar instance. 15 | #' bar <- bar_factory$get("modern") 16 | #' 17 | #' # Check the class of the bar instance. 18 | #' class(bar) 19 | #' 20 | #' # Get a basic bar instance. 21 | #' bar <- bar_factory$get("basic") 22 | #' 23 | #' # Check the class of the bar instance. 24 | #' class(bar) 25 | #' 26 | #' @seealso 27 | #' [`parabar::Bar`], [`parabar::BasicBar`], and [`parabar::ModernBar`]. 28 | #' 29 | #' @export 30 | BarFactory <- R6::R6Class("BarFactory", 31 | public = list( 32 | #' @description 33 | #' Obtain a concrete implementation of the abstract [`parabar::Bar`] 34 | #' class of the specified type. 35 | #' 36 | #' @param type A character string specifying the type of the 37 | #' [`parabar::Bar`] to instantiate. Possible values are `"modern"` and 38 | #' `"basic"`. See the **Details** section for more information. 39 | #' 40 | #' @details 41 | #' When `type = "modern"` a [`parabar::ModernBar`] instance is created 42 | #' and returned. When `type = "basic"` a [`parabar::BasicBar`] instance 43 | #' is provided instead. 44 | #' 45 | #' @return 46 | #' A concrete implementation of the class [`parabar::Bar`]. It throws an 47 | #' error if the requested bar `type` is not supported. 48 | get = function(type) { 49 | return( 50 | switch(type, 51 | basic = BasicBar$new(), 52 | modern = ModernBar$new(), 53 | Exception$feature_not_developed() 54 | ) 55 | ) 56 | } 57 | ) 58 | ) 59 | -------------------------------------------------------------------------------- /R/ContextFactory.R: -------------------------------------------------------------------------------- 1 | #' @include Exception.R Context.R ProgressTrackingContext.R 2 | 3 | #' @title ContextFactory 4 | #' 5 | #' @description 6 | #' This class is a factory that provides instances of the [`parabar::Context`] 7 | #' class. 8 | #' 9 | #' @examples 10 | #' # Create a context factory. 11 | #' context_factory <- ContextFactory$new() 12 | #' 13 | #' # Get a regular context instance. 14 | #' context <- context_factory$get("regular") 15 | #' 16 | #' # Check the class of the context instance. 17 | #' class(context) 18 | #' 19 | #' # Get a progress context instance. 20 | #' context <- context_factory$get("progress") 21 | #' class(context) 22 | #' 23 | #' @seealso 24 | #' [`parabar::Context`], [`parabar::ProgressTrackingContext`], 25 | #' [`parabar::BackendService`], and [`parabar::Backend`] 26 | #' 27 | #' @export 28 | ContextFactory <- R6::R6Class("ContextFactory", 29 | public = list( 30 | #' @description 31 | #' Obtain instances of the [`parabar::Context`] class. 32 | #' 33 | #' @param type A character string specifying the type of the 34 | #' [`parabar::Context`] to instantiate. Possible values are `"regular"` 35 | #' and `"progress"`. See the **Details** section for more information. 36 | #' 37 | #' @details 38 | #' When `type = "regular"` a [`parabar::Context`] instance is created 39 | #' and returned. When `type = "progress"` a 40 | #' [`parabar::ProgressTrackingContext`] instance is provided instead. 41 | #' 42 | #' @return 43 | #' An object of type [`parabar::Context`]. It throws an error if the 44 | #' requested context `type` is not supported. 45 | get = function(type) { 46 | return( 47 | switch(type, 48 | regular = Context$new(), 49 | progress = ProgressTrackingContext$new(), 50 | Exception$feature_not_developed() 51 | ) 52 | ) 53 | } 54 | ) 55 | ) 56 | -------------------------------------------------------------------------------- /tests/testthat/test-options.R: -------------------------------------------------------------------------------- 1 | # Test `Options` class. 2 | 3 | test_that("'Options' class fields work correctly", { 4 | # Create an instance of the Options class. 5 | options <- Options$new() 6 | 7 | # Expect that the public fields have the correct defaults. 8 | expect_true(options$progress_track) 9 | expect_equal(options$progress_timeout, 0.001) 10 | expect_equal(options$progress_bar_type, "modern") 11 | expect_equal(options$stop_forceful, FALSE) 12 | 13 | # Expect the correct default config for the modern progress bar. 14 | expect_equal( 15 | options$progress_bar_config$modern, 16 | list( 17 | show_after = 0, 18 | format = " > completed :current out of :total tasks [:percent] [:elapsed]" 19 | ) 20 | ) 21 | 22 | # Expect the correct default config for the basic progress bar. 23 | expect_equal( 24 | options$progress_bar_config$basic, 25 | list() 26 | ) 27 | 28 | # Create temporary paths for the progress log. 29 | log_path_1 <- options$progress_log_path 30 | log_path_2 <- options$progress_log_path 31 | 32 | # Expect the temporary paths to be different. 33 | expect_false(log_path_1 == log_path_2) 34 | 35 | # Set a custom path for the progress log. 36 | log_path_custom <- "custom_path.log" 37 | 38 | # Set the path on the options instance. 39 | options$progress_log_path <- log_path_custom 40 | 41 | # Expect that the log path is not fixed to the custom one. 42 | expect_equal(options$progress_log_path, log_path_custom) 43 | 44 | # Expect that subsequent calls to the log path yield the fixed path. 45 | expect_equal(options$progress_log_path, log_path_custom) 46 | 47 | # Reset the progress_log_path to default (i.e., enabling temporary paths). 48 | options$progress_log_path <- NULL 49 | 50 | # Generate a temporary log path. 51 | log_path_3 <- options$progress_log_path 52 | 53 | # Expect the log path is a temporary one again. 54 | expect_false(log_path_3 == log_path_custom) 55 | }) 56 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # R CMD check workflow. 2 | name: R-CMD-check 3 | 4 | # Set workflow permissions. 5 | permissions: read-all 6 | 7 | # Run on pushes and pull requests to the `main` branch. 8 | on: 9 | push: 10 | branches: 11 | - main 12 | pull_request: 13 | branches: 14 | - main 15 | # Trigger manually via the GitHub UI. 16 | workflow_dispatch: 17 | 18 | # Jobs to run. 19 | jobs: 20 | 21 | # CRAN check job 22 | R-CMD-check: 23 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 24 | runs-on: ${{ matrix.config.os }} 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | config: 29 | - {os: macos-latest, r: 'release'} 30 | - {os: windows-latest, r: 'release'} 31 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 32 | - {os: ubuntu-latest, r: 'release'} 33 | - {os: ubuntu-latest, r: 'oldrel-1'} 34 | env: 35 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 36 | R_KEEP_PKG_SOURCE: yes 37 | 38 | # Actions. 39 | steps: 40 | # Checkout the repository. 41 | - name: Checkout repository 42 | uses: actions/checkout@v4 43 | 44 | # Setup `pandoc`. 45 | - name: Setup pandoc 46 | uses: r-lib/actions/setup-pandoc@v2 47 | 48 | # Setup `R`. 49 | - name: Setup R 50 | uses: r-lib/actions/setup-r@v2 51 | with: 52 | r-version: ${{ matrix.config.r }} 53 | http-user-agent: ${{ matrix.config.http-user-agent }} 54 | use-public-rspm: true 55 | 56 | # Setup package dependencies and extras. 57 | - name: Install dependencies 58 | uses: r-lib/actions/setup-r-dependencies@v2 59 | with: 60 | extra-packages: any::rcmdcheck 61 | needs: check 62 | 63 | # Perform the CRAN check. 64 | - name: Check package 65 | uses: r-lib/actions/check-r-package@v2 66 | with: 67 | upload-snapshots: true 68 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 69 | -------------------------------------------------------------------------------- /tests/testthat/test-sync-backend.R: -------------------------------------------------------------------------------- 1 | # Test `SyncBackend` class. 2 | 3 | test_that("'SyncBackend' creates and manages clusters correctly", { 4 | # Create a specification. 5 | specification <- Specification$new() 6 | 7 | # Set the number of cores. 8 | specification$set_cores(cores = 2) 9 | 10 | # Pick a cluster type. 11 | cluster_type <- pick_cluster_type(specification$types) 12 | 13 | # Set the cluster type. 14 | specification$set_type(type = cluster_type) 15 | 16 | # Create a synchronous backend object. 17 | backend <- SyncBackend$new() 18 | 19 | # Expect the backend to not support progress tracking. 20 | expect_false(backend$supports_progress) 21 | 22 | # Start the cluster on the backend. 23 | backend$start(specification) 24 | 25 | # Expect the cluster to be an object of `parallel` class. 26 | expect_true(is(backend$cluster, "cluster")) 27 | 28 | # Expect that the cluster is of correct size. 29 | expect_equal(length(backend$cluster), specification$cores) 30 | 31 | # Expect that the cluster is of correct type. 32 | switch(cluster_type, 33 | "psock" = expect_true(all(tolower(summary(backend$cluster)[, 2]) == "socknode")), 34 | "fork" = expect_true(all(tolower(summary(backend$cluster)[, 2]) == "forknode")) 35 | ) 36 | 37 | # Test backend states. 38 | tests_set_for_backend_states(backend, specification) 39 | }) 40 | 41 | 42 | test_that("'SyncBackend' performs operations on the cluster correctly", { 43 | # Create a specification. 44 | specification <- Specification$new() 45 | 46 | # Set the number of cores. 47 | specification$set_cores(cores = 2) 48 | 49 | # Determine the cluster type. 50 | cluster_type <- pick_cluster_type(specification$types) 51 | 52 | # Set the cluster type. 53 | specification$set_type(type = cluster_type) 54 | 55 | # Create a synchronous backend object. 56 | backend <- SyncBackend$new() 57 | 58 | # Perform tests for synchronous backend operations. 59 | tests_set_for_synchronous_backend_operations(backend, specification, test_task) 60 | }) 61 | -------------------------------------------------------------------------------- /R/BackendFactory.R: -------------------------------------------------------------------------------- 1 | #' @include Exception.R SyncBackend.R AsyncBackend.R 2 | 3 | #' @title BackendFactory 4 | #' 5 | #' @description 6 | #' This class is a factory that provides concrete implementations of the 7 | #' [`parabar::Backend`] abstract class. 8 | #' 9 | #' @examples 10 | #' # Create a backend factory. 11 | #' backend_factory <- BackendFactory$new() 12 | #' 13 | #' # Get a synchronous backend instance. 14 | #' backend <- backend_factory$get("sync") 15 | #' 16 | #' # Check the class of the backend instance. 17 | #' class(backend) 18 | #' 19 | #' # Get an asynchronous backend instance. 20 | #' backend <- backend_factory$get("async") 21 | #' 22 | #' # Check the class of the backend instance. 23 | #' class(backend) 24 | #' 25 | #' @seealso 26 | #' [`parabar::BackendService`], [`parabar::Backend`], [`parabar::SyncBackend`], 27 | #' [`parabar::AsyncBackend`], and [`parabar::ContextFactory`]. 28 | #' 29 | #' @export 30 | BackendFactory <- R6::R6Class("BackendFactory", 31 | public = list( 32 | #' @description 33 | #' Obtain a concrete implementation of the abstract [`parabar::Backend`] 34 | #' class of the specified type. 35 | #' 36 | #' @param type A character string specifying the type of the 37 | #' [`parabar::Backend`] to instantiate. Possible values are `"sync"` and 38 | #' `"async"`. See the **Details** section for more information. 39 | #' 40 | #' @details 41 | #' When `type = "sync"` a [`parabar::SyncBackend`] instance is created 42 | #' and returned. When `type = "async"` an [`parabar::AsyncBackend`] 43 | #' instance is provided instead. 44 | #' 45 | #' @return 46 | #' A concrete implementation of the class [`parabar::Backend`]. It 47 | #' throws an error if the requested backend `type` is not supported. 48 | get = function(type) { 49 | return( 50 | switch(type, 51 | sync = SyncBackend$new(), 52 | async = AsyncBackend$new(), 53 | Exception$feature_not_developed() 54 | ) 55 | ) 56 | } 57 | ) 58 | ) 59 | -------------------------------------------------------------------------------- /man-roxygen/configure-bar.R: -------------------------------------------------------------------------------- 1 | #' @title 2 | #' Configure The Progress Bar 3 | #' 4 | #' @description 5 | #' This function can be used to conveniently configure the progress bar by 6 | #' adjusting the `progress_bar_config` field of the 7 | #' [`Options`][`parabar::Options`] instance in the [`base::.Options`] list. 8 | #' 9 | #' @param type A character string specifying the type of progress bar to be used 10 | #' with compatible [`backends`][`parabar::Backend`]. Possible values are 11 | #' `"modern"` and `"basic"`. The default value is `"modern"`. 12 | #' 13 | #' @param ... A list of named arguments used to configure the progress bar. See 14 | #' the **Details** section for more information. 15 | #' 16 | #' @details 17 | #' The optional `...` named arguments depend on the `type` of progress bar being 18 | #' configured. When `type = "modern"`, the `...` take the named arguments of the 19 | #' [`progress::progress_bar`] class. When `type = "basic"`, the `...` take the 20 | #' named arguments of the [utils::txtProgressBar()] built-in function. See the 21 | #' **Examples** section for a demonstration. 22 | #' 23 | #' @return 24 | #' The function returns void. It throws an error if the requested bar `type` is 25 | #' not supported. 26 | #' 27 | #' @examples 28 | #' # Set the default package options. 29 | #' set_default_options() 30 | #' 31 | #' # Get the progress bar type from options. 32 | #' get_option("progress_bar_type") 33 | #' 34 | #' # Get the progress bar configuration from options. 35 | #' get_option("progress_bar_config") 36 | #' 37 | #' # Adjust the format of the `modern` progress bar. 38 | #' configure_bar(type = "modern", format = "[:bar] :percent") 39 | #' 40 | #' # Check that the configuration has been updated in the options. 41 | #' get_option("progress_bar_config") 42 | #' 43 | #' # Change to and adjust the style of the `basic` progress bar. 44 | #' configure_bar(type = "basic", style = 3) 45 | #' 46 | #' # Check that the configuration has been updated in the options. 47 | #' get_option("progress_bar_type") 48 | #' get_option("progress_bar_config") 49 | #' 50 | #' @seealso 51 | #' [`progress::progress_bar`], [utils::txtProgressBar()], 52 | #' [parabar::set_default_options()], [parabar::get_option()], 53 | #' [parabar::set_option()] 54 | -------------------------------------------------------------------------------- /pkgdown/extra.scss: -------------------------------------------------------------------------------- 1 | @media (min-width: 1400px) { 2 | body { 3 | font-size: var(--bs-body-font-size); 4 | } 5 | } 6 | 7 | .container { 8 | main { 9 | margin-top: 0; 10 | 11 | // Hide redundant logo inserted by `pkgdown`. 12 | .page-header { 13 | img { 14 | display: none; 15 | } 16 | } 17 | 18 | div.sourceCode pre, .citation pre { 19 | box-shadow: 0 3px 6px rgba(0,0,0,0.16),0 3px 6px rgba(0,0,0,0.23); 20 | border-radius: .5rem; 21 | padding-left: 1rem; 22 | padding-right: 1rem; 23 | } 24 | 25 | .section { 26 | img { 27 | box-shadow: 0 3px 6px rgba(0,0,0,0.16),0 3px 6px rgba(0,0,0,0.23); 28 | padding: 1rem; 29 | border-radius: 1rem; 30 | } 31 | 32 | .license-cc img { 33 | padding: 0; 34 | box-shadow: none; 35 | border-radius: 0; 36 | margin-left: 3px; 37 | } 38 | } 39 | 40 | #additional-operations ~ table { 41 | display: table; 42 | 43 | col { 44 | &:first-of-type { 45 | width: 55%; 46 | } 47 | 48 | &:last-of-type { 49 | width: 45%; 50 | } 51 | } 52 | } 53 | } 54 | 55 | .row { 56 | display: flex; 57 | justify-content: space-between; 58 | box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22); 59 | border-radius: 1rem; 60 | padding: 2rem; 61 | margin-top: 5.25rem; 62 | } 63 | } 64 | 65 | .row > aside { 66 | margin-top: 0; 67 | } 68 | 69 | aside { 70 | div { 71 | box-shadow: 0 3px 6px rgba(0,0,0,0.16),0 3px 6px rgba(0,0,0,0.23); 72 | padding: 1rem 1rem; 73 | border-radius: 1rem; 74 | margin-bottom: 1.5rem; 75 | 76 | h2 { 77 | margin-top: 0; 78 | } 79 | 80 | ul { 81 | margin-bottom: 0; 82 | 83 | li:last-of-type { 84 | margin-bottom: 0; 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /man/Exception.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/Exception.R 3 | \name{Exception} 4 | \alias{Exception} 5 | \title{Package Exceptions} 6 | \format{ 7 | \describe{ 8 | \item{\code{Exception$abstract_class_not_instantiable(object)}}{Exception for instantiating abstract classes or interfaces.} 9 | \item{\code{Exception$method_not_implemented()}}{Exception for calling methods without an implementation.} 10 | \item{\code{Exception$feature_not_developed()}}{Exception for running into things not yet developed.} 11 | \item{\code{Exception$not_enough_cores()}}{Exception for requesting more cores than available on the machine.} 12 | \item{\code{Exception$cluster_active()}}{Exception for attempting to start a cluster while another one is active.} 13 | \item{\code{Exception$cluster_not_active()}}{Exception for attempting to stop a cluster while not active.} 14 | \item{\code{Exception$async_task_not_started()}}{Exception for reading results while an asynchronous task has not yet started.} 15 | \item{\code{Exception$async_task_running()}}{Exception for reading results while an asynchronous task is running.} 16 | \item{\code{Exception$async_task_completed()}}{Exception for reading results while a completed asynchronous task has unread results.} 17 | \item{\code{Exception$async_task_error(error)}}{Exception for errors while running an asynchronous task.} 18 | \item{\code{Exception$stop_busy_backend_not_allowed()}}{Exception for stopping a busy backend without intent.} 19 | \item{\code{Exception$temporary_file_creation_failed()}}{Exception for reading results while an asynchronous task is running.} 20 | \item{\code{Exception$type_not_assignable(actual, expected)}}{Exception for when providing incorrect object types.} 21 | \item{\code{Exception$unknown_package_option(option)}}{Exception for when requesting unknown package options.} 22 | \item{\code{Exception$primitive_as_task_not_allowed()}}{Exception for when decorating primitive functions with progress tracking.} 23 | \item{\code{Exception$array_margins_not_compatible(actual, allowed)}}{Exception for using improper margins in the \code{BackendService$apply} operation.} 24 | } 25 | } 26 | \description{ 27 | This class contains static methods for throwing exceptions with informative 28 | messages. 29 | } 30 | 31 | -------------------------------------------------------------------------------- /R/logo.R: -------------------------------------------------------------------------------- 1 | #' @template make-logo 2 | #' @export 3 | make_logo <- function(template = "./inst/assets/logo/parabar-logo.txt", version = c(1, 0, 0)) { 4 | # Load the ASCII logo. 5 | logo <- readLines(template) 6 | 7 | # Create a text connection. 8 | connection <- textConnection(NULL, open = "w", local = TRUE) 9 | 10 | # Close the connection on exit. 11 | on.exit({ 12 | # Close. 13 | close(connection) 14 | }) 15 | 16 | # Redirect console output. 17 | sink(connection, type = "output") 18 | 19 | # Parse the logo. 20 | logo <- dput(logo) 21 | 22 | # Remove output redirection. 23 | sink(NULL) 24 | 25 | # Update versioning. 26 | logo <- gsub("{{major}}", version[1], logo, perl = TRUE) 27 | logo <- gsub("{{minor}}", version[2], logo, perl = TRUE) 28 | logo <- gsub("{{patch}}", version[3], logo, perl = TRUE) 29 | 30 | # Condensed version. 31 | logo <- paste(logo, collapse = "\n") 32 | 33 | return(logo) 34 | } 35 | 36 | 37 | #' @title 38 | #' The Package Logo 39 | #' 40 | #' @description 41 | #' The logo is generated by [parabar::make_logo()] and displayed on package 42 | #' attach for interactive `R` sessions. 43 | #' 44 | #' @format 45 | #' An object of class `character` containing the `ASCII` logo. 46 | #' 47 | #' @examples 48 | #' print(LOGO) 49 | #' 50 | #' @seealso [parabar::make_logo()] 51 | #' 52 | #' @export 53 | LOGO = ". . . . . . . . . . . . . . . . . . . . . . . . . . .\n. _ v1.x.x .\n. | | .\n. _ __ __ _ _ __ __ _ | |__ __ _ _ __ .\n. | '_ \\ / _` || '__| / _` || '_ \\ / _` || '__| .\n. | |_) || (_| || | | (_| || |_) || (_| || | .\n. | .__/ \\____||_| \\____||____/ \\____||_| .\n. | | .\n. |_| .\n. .\n. . . . . . . . . . . . . . . . . . . . . . . . . . .\n. . .\n. https://parabar.mihaiconstantin.com .\n. . .\n. . . . . . . . . . . . . . . . . . . . . . . . . . ." 54 | 55 | # Add package logo class. 56 | class(LOGO) <- "parabar" 57 | 58 | # Add S3 method for properly printing the logo. 59 | #' @export 60 | print.parabar <- function(x, ...) { 61 | # Print the logo. 62 | cat(LOGO, sep = "") 63 | } 64 | -------------------------------------------------------------------------------- /tests/testthat/test-context.R: -------------------------------------------------------------------------------- 1 | # Test `Context` class. 2 | 3 | test_that("'Context' sets the backend correctly", { 4 | # Create a backend factory. 5 | backend_factory <- BackendFactory$new() 6 | 7 | # Create a base context object. 8 | context <- Context$new() 9 | 10 | # Expect the context to start with no backend. 11 | expect_null(context$backend) 12 | 13 | # Get a synchronous backend instance. 14 | backend <- backend_factory$get("sync") 15 | 16 | # Register the backend with the context. 17 | context$set_backend(backend) 18 | 19 | # Expect the registered backend to be of correct type. 20 | expect_equal( 21 | Helper$get_class_name(context$backend), 22 | Helper$get_class_name(backend) 23 | ) 24 | 25 | # Get an asynchronous backend instance. 26 | backend <- backend_factory$get("async") 27 | 28 | # Register the backend with the same context object. 29 | context$set_backend(backend) 30 | 31 | # Expect the registered backend to be of correct type. 32 | expect_equal( 33 | Helper$get_class_name(context$backend), 34 | Helper$get_class_name(backend) 35 | ) 36 | }) 37 | 38 | 39 | test_that("'Context' performs operations on the cluster correctly", { 40 | # Create a specification. 41 | specification <- Specification$new() 42 | 43 | # Set the number of cores. 44 | specification$set_cores(cores = 2) 45 | 46 | # Select a cluster type. 47 | cluster_type <- pick_cluster_type(specification$types) 48 | 49 | # Ser the cluster type. 50 | specification$set_type(type = cluster_type) 51 | 52 | # Create a backend factory. 53 | backend_factory <- BackendFactory$new() 54 | 55 | # Create a base context object. 56 | context <- Context$new() 57 | 58 | # Get a synchronous backend instance. 59 | backend <- backend_factory$get("sync") 60 | 61 | # Register the synchronous backend with the context. 62 | context$set_backend(backend) 63 | 64 | # Perform tests for synchronous backend operations. 65 | tests_set_for_synchronous_backend_operations(context, specification, test_task) 66 | 67 | # Get an asynchronous backend instance. 68 | backend <- backend_factory$get("async") 69 | 70 | # Register the asynchronous backend with the context. 71 | context$set_backend(backend) 72 | 73 | # Perform tests for synchronous backend operations. 74 | tests_set_for_asynchronous_backend_operations(context, specification, test_task) 75 | }) 76 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow name. 2 | name: test-coverage 3 | 4 | # Set workflow permissions. 5 | permissions: read-all 6 | 7 | # Events. 8 | on: 9 | push: 10 | branches: 11 | - main 12 | pull_request: 13 | branches: 14 | - main 15 | # Trigger manually via the GitHub UI. 16 | workflow_dispatch: 17 | 18 | # Jobs. 19 | jobs: 20 | 21 | # Test coverage job. 22 | test-coverage: 23 | runs-on: ubuntu-latest 24 | env: 25 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 26 | 27 | # Actions. 28 | steps: 29 | # Checkout. 30 | - name: Checkout repository 31 | uses: actions/checkout@v4 32 | 33 | # Setup `R`. 34 | - name: Setup R 35 | uses: r-lib/actions/setup-r@v2 36 | with: 37 | use-public-rspm: true 38 | 39 | # Install package dependencies and `covr` package. 40 | - name: Install dependencies 41 | uses: r-lib/actions/setup-r-dependencies@v2 42 | with: 43 | extra-packages: any::covr, any::xml2 44 | needs: coverage 45 | 46 | # Run the test coverage excluding specific lines. 47 | - name: Test coverage 48 | run: | 49 | cov <- covr::package_coverage( 50 | quiet = FALSE, 51 | clean = FALSE, 52 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package"), 53 | line_exclusions = list("R/parabar-package.R", "R/UserApiConsumer.R" = c(118:136)) 54 | ) 55 | covr::to_cobertura(cov) 56 | shell: Rscript {0} 57 | 58 | # Upload the coverage report to `Codecov``. 59 | - name: Upload coverage 60 | uses: codecov/codecov-action@v4 61 | with: 62 | fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }} 63 | file: ./cobertura.xml 64 | plugin: noop 65 | disable_search: true 66 | token: ${{ secrets.CODECOV_TOKEN }} 67 | 68 | # Show the output of `testthat`. 69 | - name: Show testthat output 70 | if: always() 71 | run: | 72 | ## -------------------------------------------------------------------- 73 | find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true 74 | shell: bash 75 | 76 | # Upload results on failure. 77 | - name: Upload test results 78 | if: failure() 79 | uses: actions/upload-artifact@v4 80 | with: 81 | name: coverage-test-failures 82 | path: ${{ runner.temp }}/package 83 | -------------------------------------------------------------------------------- /man/configure_bar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exports.R 3 | \name{configure_bar} 4 | \alias{configure_bar} 5 | \title{Configure The Progress Bar} 6 | \usage{ 7 | configure_bar(type = "modern", ...) 8 | } 9 | \arguments{ 10 | \item{type}{A character string specifying the type of progress bar to be used 11 | with compatible \code{\link[=Backend]{backends}}. Possible values are 12 | \code{"modern"} and \code{"basic"}. The default value is \code{"modern"}.} 13 | 14 | \item{...}{A list of named arguments used to configure the progress bar. See 15 | the \strong{Details} section for more information.} 16 | } 17 | \value{ 18 | The function returns void. It throws an error if the requested bar \code{type} is 19 | not supported. 20 | } 21 | \description{ 22 | This function can be used to conveniently configure the progress bar by 23 | adjusting the \code{progress_bar_config} field of the 24 | \code{\link[=Options]{Options}} instance in the \code{\link[base:options]{base::.Options}} list. 25 | } 26 | \details{ 27 | The optional \code{...} named arguments depend on the \code{type} of progress bar being 28 | configured. When \code{type = "modern"}, the \code{...} take the named arguments of the 29 | \code{\link[progress:progress_bar]{progress::progress_bar}} class. When \code{type = "basic"}, the \code{...} take the 30 | named arguments of the \code{\link[utils:txtProgressBar]{utils::txtProgressBar()}} built-in function. See the 31 | \strong{Examples} section for a demonstration. 32 | } 33 | \examples{ 34 | # Set the default package options. 35 | set_default_options() 36 | 37 | # Get the progress bar type from options. 38 | get_option("progress_bar_type") 39 | 40 | # Get the progress bar configuration from options. 41 | get_option("progress_bar_config") 42 | 43 | # Adjust the format of the `modern` progress bar. 44 | configure_bar(type = "modern", format = "[:bar] :percent") 45 | 46 | # Check that the configuration has been updated in the options. 47 | get_option("progress_bar_config") 48 | 49 | # Change to and adjust the style of the `basic` progress bar. 50 | configure_bar(type = "basic", style = 3) 51 | 52 | # Check that the configuration has been updated in the options. 53 | get_option("progress_bar_type") 54 | get_option("progress_bar_config") 55 | 56 | } 57 | \seealso{ 58 | \code{\link[progress:progress_bar]{progress::progress_bar}}, \code{\link[utils:txtProgressBar]{utils::txtProgressBar()}}, 59 | \code{\link[=set_default_options]{set_default_options()}}, \code{\link[=get_option]{get_option()}}, 60 | \code{\link[=set_option]{set_option()}} 61 | } 62 | -------------------------------------------------------------------------------- /R/Bar.R: -------------------------------------------------------------------------------- 1 | #' @include Exception.R 2 | 3 | #' @title 4 | #' Bar 5 | #' 6 | #' @description 7 | #' This is an abstract class that defines the pure virtual methods a concrete 8 | #' bar must implement. 9 | #' 10 | #' @details 11 | #' This class cannot be instantiated. It needs to be extended by concrete 12 | #' subclasses that implement the pure virtual methods. Instances of concrete 13 | #' backend implementations can be conveniently obtained using the 14 | #' [`parabar::BarFactory`] class. 15 | #' 16 | #' @seealso 17 | #' [`parabar::BasicBar`], [`parabar::ModernBar`], and [`parabar::BarFactory`]. 18 | #' 19 | #' @export 20 | Bar <- R6::R6Class("Bar", 21 | private = list( 22 | .bar = NULL 23 | ), 24 | 25 | public = list( 26 | #' @description 27 | #' Create a new [`parabar::Bar`] object. 28 | #' 29 | #' @return 30 | #' Instantiating this class will throw an error. 31 | initialize = function() { 32 | Exception$abstract_class_not_instantiable(self) 33 | }, 34 | 35 | #' @description 36 | #' Create a progress bar. 37 | #' 38 | #' @param total The total number of times the progress bar should tick. 39 | #' 40 | #' @param initial The starting point of the progress bar. 41 | #' 42 | #' @param ... Additional arguments for the bar creation. See the 43 | #' **Details** section for more information. 44 | #' 45 | #' @details 46 | #' The optional `...` named arguments depend on the specific concrete 47 | #' implementation (i.e., [`parabar::BasicBar`] or 48 | #' [`parabar::ModernBar`]). 49 | #' 50 | #' @return 51 | #' This method returns void. The resulting bar is stored in the private 52 | #' field `.bar`, accessible via the active binding `engine`. 53 | create = function(total, initial, ...) { 54 | Exception$method_not_implemented() 55 | }, 56 | 57 | #' @description 58 | #' Update the progress bar. 59 | #' 60 | #' @param current The position the progress bar should be at (e.g., 30 61 | #' out of 100), usually the index in a loop. 62 | update = function(current) { 63 | Exception$method_not_implemented() 64 | }, 65 | 66 | #' @description 67 | #' Terminate the progress bar. 68 | terminate = function() { 69 | Exception$method_not_implemented() 70 | } 71 | ), 72 | 73 | active = list( 74 | #' @field engine The bar engine. 75 | engine = function() { return(private$.bar) } 76 | ) 77 | ) 78 | -------------------------------------------------------------------------------- /man/BarFactory.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/BarFactory.R 3 | \name{BarFactory} 4 | \alias{BarFactory} 5 | \title{BackendFactory} 6 | \description{ 7 | This class is a factory that provides concrete implementations of the 8 | \code{\link{Bar}} abstract class. 9 | } 10 | \examples{ 11 | # Create a bar factory. 12 | bar_factory <- BarFactory$new() 13 | 14 | # Get a modern bar instance. 15 | bar <- bar_factory$get("modern") 16 | 17 | # Check the class of the bar instance. 18 | class(bar) 19 | 20 | # Get a basic bar instance. 21 | bar <- bar_factory$get("basic") 22 | 23 | # Check the class of the bar instance. 24 | class(bar) 25 | 26 | } 27 | \seealso{ 28 | \code{\link{Bar}}, \code{\link{BasicBar}}, and \code{\link{ModernBar}}. 29 | } 30 | \section{Methods}{ 31 | \subsection{Public methods}{ 32 | \itemize{ 33 | \item \href{#method-BarFactory-get}{\code{BarFactory$get()}} 34 | \item \href{#method-BarFactory-clone}{\code{BarFactory$clone()}} 35 | } 36 | } 37 | \if{html}{\out{
}} 38 | \if{html}{\out{}} 39 | \if{latex}{\out{\hypertarget{method-BarFactory-get}{}}} 40 | \subsection{Method \code{get()}}{ 41 | Obtain a concrete implementation of the abstract \code{\link{Bar}} 42 | class of the specified type. 43 | \subsection{Usage}{ 44 | \if{html}{\out{
}}\preformatted{BarFactory$get(type)}\if{html}{\out{
}} 45 | } 46 | 47 | \subsection{Arguments}{ 48 | \if{html}{\out{
}} 49 | \describe{ 50 | \item{\code{type}}{A character string specifying the type of the 51 | \code{\link{Bar}} to instantiate. Possible values are \code{"modern"} and 52 | \code{"basic"}. See the \strong{Details} section for more information.} 53 | } 54 | \if{html}{\out{
}} 55 | } 56 | \subsection{Details}{ 57 | When \code{type = "modern"} a \code{\link{ModernBar}} instance is created 58 | and returned. When \code{type = "basic"} a \code{\link{BasicBar}} instance 59 | is provided instead. 60 | } 61 | 62 | \subsection{Returns}{ 63 | A concrete implementation of the class \code{\link{Bar}}. It throws an 64 | error if the requested bar \code{type} is not supported. 65 | } 66 | } 67 | \if{html}{\out{
}} 68 | \if{html}{\out{}} 69 | \if{latex}{\out{\hypertarget{method-BarFactory-clone}{}}} 70 | \subsection{Method \code{clone()}}{ 71 | The objects of this class are cloneable with this method. 72 | \subsection{Usage}{ 73 | \if{html}{\out{
}}\preformatted{BarFactory$clone(deep = FALSE)}\if{html}{\out{
}} 74 | } 75 | 76 | \subsection{Arguments}{ 77 | \if{html}{\out{
}} 78 | \describe{ 79 | \item{\code{deep}}{Whether to make a deep clone.} 80 | } 81 | \if{html}{\out{
}} 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /R/Backend.R: -------------------------------------------------------------------------------- 1 | #' @include Exception.R BackendService.R 2 | 3 | #' @title 4 | #' Backend 5 | #' 6 | #' @description 7 | #' This is an abstract class that serves as a base class for all concrete 8 | #' backend implementations. It defines the common properties that all concrete 9 | #' backends require. 10 | #' 11 | #' @details 12 | #' This class cannot be instantiated. It needs to be extended by concrete 13 | #' subclasses that implement the pure virtual methods. Instances of concrete 14 | #' backend implementations can be conveniently obtained using the 15 | #' [`parabar::BackendFactory`] class. 16 | #' 17 | #' @seealso 18 | #' [`parabar::BackendService`], [`parabar::SyncBackend`], 19 | #' [`parabar::AsyncBackend`], [`parabar::BackendFactory`], and 20 | #' [`parabar::Context`]. 21 | #' 22 | #' @export 23 | Backend <- R6::R6Class("Backend", 24 | inherit = BackendService, 25 | 26 | private = list( 27 | # The engine used to dispatch the tasks. 28 | .cluster = NULL, 29 | 30 | # The progress tracking capabilities of the backend implementation. 31 | .supports_progress = FALSE, 32 | 33 | # The results of running the task on the backend. 34 | .output = NULL, 35 | 36 | # Whether the backend contains an active cluster. 37 | .active = FALSE, 38 | 39 | # Toggle active flag. 40 | .toggle_active_state = function() { 41 | # The to the opposite state. 42 | private$.active <- !private$.active 43 | } 44 | ), 45 | 46 | public = list( 47 | #' @description 48 | #' Create a new [`parabar::Backend`] object. 49 | #' 50 | #' @return 51 | #' Instantiating this class will throw an error. 52 | initialize = function() { 53 | Exception$abstract_class_not_instantiable(self) 54 | } 55 | ), 56 | 57 | active = list( 58 | #' @field cluster The cluster object used by the backend. For 59 | #' [`parabar::SyncBackend`] objects, this is a cluster object created by 60 | #' [parallel::makeCluster()]. For [`parabar::AsyncBackend`] objects, 61 | #' this is a permanent `R` session created by [`callr::r_session`] that 62 | #' contains the [parallel::makeCluster()] cluster object. 63 | cluster = function() { return(private$.cluster) }, 64 | 65 | #' @field supports_progress A boolean value indicating whether the 66 | #' backend implementation supports progress tracking. 67 | supports_progress = function() { return(private$.supports_progress) }, 68 | 69 | #' @field active A boolean value indicating whether the backend 70 | #' implementation has an active cluster. 71 | active = function() { return(private$.active) } 72 | ) 73 | ) 74 | -------------------------------------------------------------------------------- /man/ContextFactory.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ContextFactory.R 3 | \name{ContextFactory} 4 | \alias{ContextFactory} 5 | \title{ContextFactory} 6 | \description{ 7 | This class is a factory that provides instances of the \code{\link{Context}} 8 | class. 9 | } 10 | \examples{ 11 | # Create a context factory. 12 | context_factory <- ContextFactory$new() 13 | 14 | # Get a regular context instance. 15 | context <- context_factory$get("regular") 16 | 17 | # Check the class of the context instance. 18 | class(context) 19 | 20 | # Get a progress context instance. 21 | context <- context_factory$get("progress") 22 | class(context) 23 | 24 | } 25 | \seealso{ 26 | \code{\link{Context}}, \code{\link{ProgressTrackingContext}}, 27 | \code{\link{BackendService}}, and \code{\link{Backend}} 28 | } 29 | \section{Methods}{ 30 | \subsection{Public methods}{ 31 | \itemize{ 32 | \item \href{#method-ContextFactory-get}{\code{ContextFactory$get()}} 33 | \item \href{#method-ContextFactory-clone}{\code{ContextFactory$clone()}} 34 | } 35 | } 36 | \if{html}{\out{
}} 37 | \if{html}{\out{}} 38 | \if{latex}{\out{\hypertarget{method-ContextFactory-get}{}}} 39 | \subsection{Method \code{get()}}{ 40 | Obtain instances of the \code{\link{Context}} class. 41 | \subsection{Usage}{ 42 | \if{html}{\out{
}}\preformatted{ContextFactory$get(type)}\if{html}{\out{
}} 43 | } 44 | 45 | \subsection{Arguments}{ 46 | \if{html}{\out{
}} 47 | \describe{ 48 | \item{\code{type}}{A character string specifying the type of the 49 | \code{\link{Context}} to instantiate. Possible values are \code{"regular"} 50 | and \code{"progress"}. See the \strong{Details} section for more information.} 51 | } 52 | \if{html}{\out{
}} 53 | } 54 | \subsection{Details}{ 55 | When \code{type = "regular"} a \code{\link{Context}} instance is created 56 | and returned. When \code{type = "progress"} a 57 | \code{\link{ProgressTrackingContext}} instance is provided instead. 58 | } 59 | 60 | \subsection{Returns}{ 61 | An object of type \code{\link{Context}}. It throws an error if the 62 | requested context \code{type} is not supported. 63 | } 64 | } 65 | \if{html}{\out{
}} 66 | \if{html}{\out{}} 67 | \if{latex}{\out{\hypertarget{method-ContextFactory-clone}{}}} 68 | \subsection{Method \code{clone()}}{ 69 | The objects of this class are cloneable with this method. 70 | \subsection{Usage}{ 71 | \if{html}{\out{
}}\preformatted{ContextFactory$clone(deep = FALSE)}\if{html}{\out{
}} 72 | } 73 | 74 | \subsection{Arguments}{ 75 | \if{html}{\out{
}} 76 | \describe{ 77 | \item{\code{deep}}{Whether to make a deep clone.} 78 | } 79 | \if{html}{\out{
}} 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /man/clear.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exports.R 3 | \name{clear} 4 | \alias{clear} 5 | \title{Clear a Backend} 6 | \usage{ 7 | clear(backend) 8 | } 9 | \arguments{ 10 | \item{backend}{An object of class \code{\link{Backend}} as returned by the 11 | \code{\link[=start_backend]{start_backend()}} function.} 12 | } 13 | \value{ 14 | The function returns void. It throws an error if the value provided for the 15 | \code{backend} argument is not an instance of class \code{\link{Backend}}. 16 | } 17 | \description{ 18 | This function can be used to clear a \code{\link[=Backend]{backend}} created 19 | by \code{\link[=start_backend]{start_backend()}}. 20 | } 21 | \details{ 22 | This function is a convenience wrapper around the lower-lever API of 23 | \code{\link{parabar}} aimed at developers. More specifically, this function 24 | calls the \code{\link[=BackendService]{clear}} method on the provided 25 | \code{\link[=Backend]{backend}} instance. 26 | } 27 | \examples{ 28 | # Create an asynchronous backend. 29 | backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async") 30 | 31 | # Check that the backend is active. 32 | backend$active 33 | 34 | # Check if there is anything on the backend. 35 | peek(backend) 36 | 37 | # Create a dummy variable. 38 | name <- "parabar" 39 | 40 | # Export the `name` variable in the current environment to the backend. 41 | export(backend, "name", environment()) 42 | 43 | # Remove the dummy variable from the current environment. 44 | rm(name) 45 | 46 | # Check the backend to see that the variable has been exported. 47 | peek(backend) 48 | 49 | # Run an expression on the backend. 50 | # Note that the symbols in the expression are resolved on the backend. 51 | evaluate(backend, { 52 | # Print the name. 53 | print(paste0("Hello, ", name, "!")) 54 | }) 55 | 56 | # Clear the backend. 57 | clear(backend) 58 | 59 | # Check that there is nothing on the backend. 60 | peek(backend) 61 | 62 | # Use a basic progress bar (i.e., see `parabar::Bar`). 63 | configure_bar(type = "basic", style = 3) 64 | 65 | # Run a task in parallel (i.e., approx. 1.25 seconds). 66 | output <- par_sapply(backend, x = 1:10, fun = function(x) { 67 | # Sleep a bit. 68 | Sys.sleep(0.25) 69 | 70 | # Compute and return. 71 | return(x + 1) 72 | }) 73 | 74 | # Print the output. 75 | print(output) 76 | 77 | # Stop the backend. 78 | stop_backend(backend) 79 | 80 | # Check that the backend is not active. 81 | backend$active 82 | 83 | } 84 | \seealso{ 85 | \code{\link[=start_backend]{start_backend()}}, \code{\link[=peek]{peek()}}, \code{\link[=export]{export()}}, 86 | \code{\link[=evaluate]{evaluate()}}, \code{\link[=configure_bar]{configure_bar()}}, \code{\link[=par_sapply]{par_sapply()}}, 87 | \code{\link[=par_lapply]{par_lapply()}}, \code{\link[=par_apply]{par_apply()}}, \code{\link[=stop_backend]{stop_backend()}}, 88 | and \code{\link{BackendService}}. 89 | } 90 | -------------------------------------------------------------------------------- /man/BackendFactory.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/BackendFactory.R 3 | \name{BackendFactory} 4 | \alias{BackendFactory} 5 | \title{BackendFactory} 6 | \description{ 7 | This class is a factory that provides concrete implementations of the 8 | \code{\link{Backend}} abstract class. 9 | } 10 | \examples{ 11 | # Create a backend factory. 12 | backend_factory <- BackendFactory$new() 13 | 14 | # Get a synchronous backend instance. 15 | backend <- backend_factory$get("sync") 16 | 17 | # Check the class of the backend instance. 18 | class(backend) 19 | 20 | # Get an asynchronous backend instance. 21 | backend <- backend_factory$get("async") 22 | 23 | # Check the class of the backend instance. 24 | class(backend) 25 | 26 | } 27 | \seealso{ 28 | \code{\link{BackendService}}, \code{\link{Backend}}, \code{\link{SyncBackend}}, 29 | \code{\link{AsyncBackend}}, and \code{\link{ContextFactory}}. 30 | } 31 | \section{Methods}{ 32 | \subsection{Public methods}{ 33 | \itemize{ 34 | \item \href{#method-BackendFactory-get}{\code{BackendFactory$get()}} 35 | \item \href{#method-BackendFactory-clone}{\code{BackendFactory$clone()}} 36 | } 37 | } 38 | \if{html}{\out{
}} 39 | \if{html}{\out{}} 40 | \if{latex}{\out{\hypertarget{method-BackendFactory-get}{}}} 41 | \subsection{Method \code{get()}}{ 42 | Obtain a concrete implementation of the abstract \code{\link{Backend}} 43 | class of the specified type. 44 | \subsection{Usage}{ 45 | \if{html}{\out{
}}\preformatted{BackendFactory$get(type)}\if{html}{\out{
}} 46 | } 47 | 48 | \subsection{Arguments}{ 49 | \if{html}{\out{
}} 50 | \describe{ 51 | \item{\code{type}}{A character string specifying the type of the 52 | \code{\link{Backend}} to instantiate. Possible values are \code{"sync"} and 53 | \code{"async"}. See the \strong{Details} section for more information.} 54 | } 55 | \if{html}{\out{
}} 56 | } 57 | \subsection{Details}{ 58 | When \code{type = "sync"} a \code{\link{SyncBackend}} instance is created 59 | and returned. When \code{type = "async"} an \code{\link{AsyncBackend}} 60 | instance is provided instead. 61 | } 62 | 63 | \subsection{Returns}{ 64 | A concrete implementation of the class \code{\link{Backend}}. It 65 | throws an error if the requested backend \code{type} is not supported. 66 | } 67 | } 68 | \if{html}{\out{
}} 69 | \if{html}{\out{}} 70 | \if{latex}{\out{\hypertarget{method-BackendFactory-clone}{}}} 71 | \subsection{Method \code{clone()}}{ 72 | The objects of this class are cloneable with this method. 73 | \subsection{Usage}{ 74 | \if{html}{\out{
}}\preformatted{BackendFactory$clone(deep = FALSE)}\if{html}{\out{
}} 75 | } 76 | 77 | \subsection{Arguments}{ 78 | \if{html}{\out{
}} 79 | \describe{ 80 | \item{\code{deep}}{Whether to make a deep clone.} 81 | } 82 | \if{html}{\out{
}} 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /man/stop_backend.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exports.R 3 | \name{stop_backend} 4 | \alias{stop_backend} 5 | \title{Stop a Backend} 6 | \usage{ 7 | stop_backend(backend) 8 | } 9 | \arguments{ 10 | \item{backend}{An object of class \code{\link{Backend}} as returned by the 11 | \code{\link[=start_backend]{start_backend()}} function.} 12 | } 13 | \value{ 14 | The function returns void. It throws an error if: 15 | \itemize{ 16 | \item the value provided for the \code{backend} argument is not an instance of class 17 | \code{\link{Backend}}. 18 | \item the \code{\link[=Backend]{backend}} object provided is already stopped 19 | (i.e., is not active). 20 | } 21 | } 22 | \description{ 23 | This function can be used to stop a \code{\link[=Backend]{backend}} created 24 | by \code{\link[=start_backend]{start_backend()}}. 25 | } 26 | \details{ 27 | This function is a convenience wrapper around the lower-lever API of 28 | \code{\link{parabar}} aimed at developers. More specifically, this function 29 | calls the \code{\link[=BackendService]{stop}} method on the provided 30 | \code{\link[=Backend]{backend}} instance. 31 | } 32 | \examples{ 33 | # Create an asynchronous backend. 34 | backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async") 35 | 36 | # Check that the backend is active. 37 | backend$active 38 | 39 | # Check if there is anything on the backend. 40 | peek(backend) 41 | 42 | # Create a dummy variable. 43 | name <- "parabar" 44 | 45 | # Export the `name` variable in the current environment to the backend. 46 | export(backend, "name", environment()) 47 | 48 | # Remove the dummy variable from the current environment. 49 | rm(name) 50 | 51 | # Check the backend to see that the variable has been exported. 52 | peek(backend) 53 | 54 | # Run an expression on the backend. 55 | # Note that the symbols in the expression are resolved on the backend. 56 | evaluate(backend, { 57 | # Print the name. 58 | print(paste0("Hello, ", name, "!")) 59 | }) 60 | 61 | # Clear the backend. 62 | clear(backend) 63 | 64 | # Check that there is nothing on the backend. 65 | peek(backend) 66 | 67 | # Use a basic progress bar (i.e., see `parabar::Bar`). 68 | configure_bar(type = "basic", style = 3) 69 | 70 | # Run a task in parallel (i.e., approx. 1.25 seconds). 71 | output <- par_sapply(backend, x = 1:10, fun = function(x) { 72 | # Sleep a bit. 73 | Sys.sleep(0.25) 74 | 75 | # Compute and return. 76 | return(x + 1) 77 | }) 78 | 79 | # Print the output. 80 | print(output) 81 | 82 | # Stop the backend. 83 | stop_backend(backend) 84 | 85 | # Check that the backend is not active. 86 | backend$active 87 | 88 | } 89 | \seealso{ 90 | \code{\link[=start_backend]{start_backend()}}, \code{\link[=peek]{peek()}}, \code{\link[=export]{export()}}, 91 | \code{\link[=evaluate]{evaluate()}}, \code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}}, 92 | \code{\link[=par_sapply]{par_sapply()}}, \code{\link[=par_apply]{par_apply()}}, \code{\link[=par_lapply]{par_lapply()}}, and 93 | \code{\link{BackendService}}. 94 | } 95 | -------------------------------------------------------------------------------- /man/peek.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exports.R 3 | \name{peek} 4 | \alias{peek} 5 | \title{Inspect a Backend} 6 | \usage{ 7 | peek(backend) 8 | } 9 | \arguments{ 10 | \item{backend}{An object of class \code{\link{Backend}} as returned by the 11 | \code{\link[=start_backend]{start_backend()}} function.} 12 | } 13 | \value{ 14 | The function returns a list of character vectors, where each list element 15 | corresponds to a node, and each element of the character vector is the name 16 | of a variable present on that node. It throws an error if the value provided 17 | for the \code{backend} argument is not an instance of class \code{\link{Backend}}. 18 | } 19 | \description{ 20 | This function can be used to check the names of the variables present on a 21 | \code{\link[=Backend]{backend}} created by \code{\link[=start_backend]{start_backend()}}. 22 | } 23 | \details{ 24 | This function is a convenience wrapper around the lower-lever API of 25 | \code{\link{parabar}} aimed at developers. More specifically, this function 26 | calls the \code{\link[=BackendService]{peek}} method on the provided 27 | \code{\link[=Backend]{backend}} instance. 28 | } 29 | \examples{ 30 | # Create an asynchronous backend. 31 | backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async") 32 | 33 | # Check that the backend is active. 34 | backend$active 35 | 36 | # Check if there is anything on the backend. 37 | peek(backend) 38 | 39 | # Create a dummy variable. 40 | name <- "parabar" 41 | 42 | # Export the `name` variable in the current environment to the backend. 43 | export(backend, "name", environment()) 44 | 45 | # Remove the dummy variable from the current environment. 46 | rm(name) 47 | 48 | # Check the backend to see that the variable has been exported. 49 | peek(backend) 50 | 51 | # Run an expression on the backend. 52 | # Note that the symbols in the expression are resolved on the backend. 53 | evaluate(backend, { 54 | # Print the name. 55 | print(paste0("Hello, ", name, "!")) 56 | }) 57 | 58 | # Clear the backend. 59 | clear(backend) 60 | 61 | # Check that there is nothing on the backend. 62 | peek(backend) 63 | 64 | # Use a basic progress bar (i.e., see `parabar::Bar`). 65 | configure_bar(type = "basic", style = 3) 66 | 67 | # Run a task in parallel (i.e., approx. 1.25 seconds). 68 | output <- par_sapply(backend, x = 1:10, fun = function(x) { 69 | # Sleep a bit. 70 | Sys.sleep(0.25) 71 | 72 | # Compute and return. 73 | return(x + 1) 74 | }) 75 | 76 | # Print the output. 77 | print(output) 78 | 79 | # Stop the backend. 80 | stop_backend(backend) 81 | 82 | # Check that the backend is not active. 83 | backend$active 84 | 85 | } 86 | \seealso{ 87 | \code{\link[=start_backend]{start_backend()}}, \code{\link[=export]{export()}}, \code{\link[=evaluate]{evaluate()}}, 88 | \code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}}, \code{\link[=par_sapply]{par_sapply()}}, 89 | \code{\link[=par_lapply]{par_lapply()}}, \code{\link[=par_apply]{par_apply()}}, \code{\link[=stop_backend]{stop_backend()}}, 90 | and \code{\link{BackendService}}. 91 | } 92 | -------------------------------------------------------------------------------- /man/evaluate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exports.R 3 | \name{evaluate} 4 | \alias{evaluate} 5 | \title{Evaluate An Expression On The Backend} 6 | \usage{ 7 | evaluate(backend, expression) 8 | } 9 | \arguments{ 10 | \item{backend}{An object of class \code{\link{Backend}} as returned by the 11 | \code{\link[=start_backend]{start_backend()}} function.} 12 | 13 | \item{expression}{An unquoted expression to evaluate on the backend.} 14 | } 15 | \value{ 16 | This method returns the result of the expression evaluation. It throws an 17 | error if the value provided for the \code{backend} argument is not an instance of 18 | class \code{\link{Backend}}. 19 | } 20 | \description{ 21 | This function can be used to evaluate an arbitrary \code{\link[base:expression]{base::expression()}} a 22 | \code{\link[=Backend]{backend}} created by \code{\link[=start_backend]{start_backend()}}. 23 | } 24 | \details{ 25 | This function is a convenience wrapper around the lower-lever API of 26 | \code{\link{parabar}} aimed at developers. More specifically, this function 27 | calls the \code{\link[=BackendService]{evaluate}} method on the provided 28 | \code{\link[=Backend]{backend}} instance. 29 | } 30 | \examples{ 31 | # Create an asynchronous backend. 32 | backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async") 33 | 34 | # Check that the backend is active. 35 | backend$active 36 | 37 | # Check if there is anything on the backend. 38 | peek(backend) 39 | 40 | # Create a dummy variable. 41 | name <- "parabar" 42 | 43 | # Export the `name` variable in the current environment to the backend. 44 | export(backend, "name", environment()) 45 | 46 | # Remove the dummy variable from the current environment. 47 | rm(name) 48 | 49 | # Check the backend to see that the variable has been exported. 50 | peek(backend) 51 | 52 | # Run an expression on the backend. 53 | # Note that the symbols in the expression are resolved on the backend. 54 | evaluate(backend, { 55 | # Print the name. 56 | print(paste0("Hello, ", name, "!")) 57 | }) 58 | 59 | # Clear the backend. 60 | clear(backend) 61 | 62 | # Check that there is nothing on the backend. 63 | peek(backend) 64 | 65 | # Use a basic progress bar (i.e., see `parabar::Bar`). 66 | configure_bar(type = "basic", style = 3) 67 | 68 | # Run a task in parallel (i.e., approx. 1.25 seconds). 69 | output <- par_sapply(backend, x = 1:10, fun = function(x) { 70 | # Sleep a bit. 71 | Sys.sleep(0.25) 72 | 73 | # Compute and return. 74 | return(x + 1) 75 | }) 76 | 77 | # Print the output. 78 | print(output) 79 | 80 | # Stop the backend. 81 | stop_backend(backend) 82 | 83 | # Check that the backend is not active. 84 | backend$active 85 | 86 | } 87 | \seealso{ 88 | \code{\link[=start_backend]{start_backend()}}, \code{\link[=peek]{peek()}}, \code{\link[=export]{export()}}, 89 | \code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}}, \code{\link[=par_sapply]{par_sapply()}}, 90 | \code{\link[=par_lapply]{par_lapply()}}, \code{\link[=par_apply]{par_apply()}}, \code{\link[=stop_backend]{stop_backend()}}, 91 | and \code{\link{BackendService}}. 92 | } 93 | -------------------------------------------------------------------------------- /man/export.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exports.R 3 | \name{export} 4 | \alias{export} 5 | \title{Export Objects To a Backend} 6 | \usage{ 7 | export(backend, variables, environment) 8 | } 9 | \arguments{ 10 | \item{backend}{An object of class \code{\link{Backend}} as returned by the 11 | \code{\link[=start_backend]{start_backend()}} function.} 12 | 13 | \item{variables}{A character vector of variable names to export to the 14 | backend.} 15 | 16 | \item{environment}{An environment from which to export the variables. If no 17 | environment is provided, the \code{.GlobalEnv} environment is used.} 18 | } 19 | \value{ 20 | The function returns void. It throws an error if the value provided for the 21 | \code{backend} argument is not an instance of class \code{\link{Backend}}. 22 | } 23 | \description{ 24 | This function can be used to export objects to a 25 | \code{\link[=Backend]{backend}} created by \code{\link[=start_backend]{start_backend()}}. 26 | } 27 | \details{ 28 | This function is a convenience wrapper around the lower-lever API of 29 | \code{\link{parabar}} aimed at developers. More specifically, this function 30 | calls the \code{\link[=BackendService]{export}} method on the provided 31 | \code{\link[=Backend]{backend}} instance. 32 | } 33 | \examples{ 34 | # Create an asynchronous backend. 35 | backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async") 36 | 37 | # Check that the backend is active. 38 | backend$active 39 | 40 | # Check if there is anything on the backend. 41 | peek(backend) 42 | 43 | # Create a dummy variable. 44 | name <- "parabar" 45 | 46 | # Export the `name` variable in the current environment to the backend. 47 | export(backend, "name", environment()) 48 | 49 | # Remove the dummy variable from the current environment. 50 | rm(name) 51 | 52 | # Check the backend to see that the variable has been exported. 53 | peek(backend) 54 | 55 | # Run an expression on the backend. 56 | # Note that the symbols in the expression are resolved on the backend. 57 | evaluate(backend, { 58 | # Print the name. 59 | print(paste0("Hello, ", name, "!")) 60 | }) 61 | 62 | # Clear the backend. 63 | clear(backend) 64 | 65 | # Check that there is nothing on the backend. 66 | peek(backend) 67 | 68 | # Use a basic progress bar (i.e., see `parabar::Bar`). 69 | configure_bar(type = "basic", style = 3) 70 | 71 | # Run a task in parallel (i.e., approx. 1.25 seconds). 72 | output <- par_sapply(backend, x = 1:10, fun = function(x) { 73 | # Sleep a bit. 74 | Sys.sleep(0.25) 75 | 76 | # Compute and return. 77 | return(x + 1) 78 | }) 79 | 80 | # Print the output. 81 | print(output) 82 | 83 | # Stop the backend. 84 | stop_backend(backend) 85 | 86 | # Check that the backend is not active. 87 | backend$active 88 | 89 | } 90 | \seealso{ 91 | \code{\link[=start_backend]{start_backend()}}, \code{\link[=peek]{peek()}}, \code{\link[=evaluate]{evaluate()}}, 92 | \code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}}, \code{\link[=par_sapply]{par_sapply()}}, 93 | \code{\link[=par_lapply]{par_lapply()}}, \code{\link[=par_apply]{par_apply()}}, \code{\link[=stop_backend]{stop_backend()}}, 94 | and \code{\link{BackendService}}. 95 | } 96 | -------------------------------------------------------------------------------- /R/Warning.R: -------------------------------------------------------------------------------- 1 | #' @include Helper.R 2 | 3 | #' @title 4 | #' Package Warnings 5 | #' 6 | #' @description 7 | #' This class contains static methods for throwing warnings with informative 8 | #' messages. 9 | #' 10 | #' @format 11 | #' \describe{ 12 | #' \item{\code{Warning$requested_cluster_cores_too_low()}}{Warning for not requesting enough cluster cores.} 13 | #' \item{\code{Warning$requested_cluster_cores_too_high()}}{Warning for requesting too many cluster cores.} 14 | #' \item{\code{Warning$requested_cluster_type_not_supported()}}{Warning for requesting an unsupported cluster type.} 15 | #' \item{\code{Warning$progress_not_supported_for_backend()}}{Warning for using a backend incompatible with progress tracking.} 16 | #' \item{\code{Warning$error_in_backend_finalizer()}}{Warning for errors in the backend finalizer during garbage collection.} 17 | #' } 18 | #' 19 | #' @export 20 | Warning <- R6::R6Class("Warning", 21 | cloneable = FALSE 22 | ) 23 | 24 | # Warning for not requesting enough cluster cores. 25 | Warning$requested_cluster_cores_too_low <- function() { 26 | # Construct the message. 27 | message <- paste0("Argument `cores` too low. Setting to 1.") 28 | 29 | # Issue the warning. 30 | warning(message, call. = FALSE) 31 | } 32 | 33 | # Warning for requesting too many cluster cores. 34 | Warning$requested_cluster_cores_too_high <- function(max_cores) { 35 | # Construct the message. 36 | message <- paste0("Argument `cores` cannot be larger than ", max_cores, ". Setting to ", max_cores, ".") 37 | 38 | # Issue the warning. 39 | warning(message, call. = FALSE) 40 | } 41 | 42 | # Warning for requesting an unsupported cluster type. 43 | Warning$requested_cluster_type_not_supported <- function(supported_types) { 44 | # Issue the warning. 45 | warning( 46 | paste0( 47 | "Argument `type` must be ", 48 | paste0("'", supported_types, "'", collapse = " or ", sep = ""), ". Defaulting to '", supported_types["windows"], "'." 49 | ), 50 | call. = FALSE 51 | ) 52 | } 53 | 54 | # Warning for requesting an incompatible cluster type. 55 | Warning$requested_cluster_type_not_compatible <- function(supported_types) { 56 | # Issue the warning. 57 | warning( 58 | paste0( 59 | "Requested cluster type not compatible. Defaulting to '", supported_types["windows"], "'." 60 | ), 61 | call. = FALSE 62 | ) 63 | } 64 | 65 | # Warning for using a backend incompatible with progress tracking. 66 | Warning$progress_not_supported_for_backend <- function(backend) { 67 | # Get backend type. 68 | type <- Helper$get_class_name(backend) 69 | 70 | # Construct warning message. 71 | message <- paste0("Progress tracking not supported for backend of type '", type, "'.") 72 | 73 | # Throw the error. 74 | warning(message, call. = FALSE) 75 | } 76 | 77 | # Warning for errors in the backend finalizer during garbage collection. 78 | Warning$error_in_backend_finalizer <- function(backend, error) { 79 | # Get the backend type. 80 | backend_type <- Helper$get_class_name(backend) 81 | 82 | # Construct the message. 83 | message <- paste0("Caught error in '", backend_type ,"' finalizer: '", error$message, "'.") 84 | 85 | # Issue the warning. 86 | warning(message, call. = FALSE) 87 | } 88 | -------------------------------------------------------------------------------- /tests/testthat/test-backend-finalizer.R: -------------------------------------------------------------------------------- 1 | # Test automatic finalizers for `SyncBackend` and `AsyncBackend` objects. 2 | 3 | test_that("'SyncBackend' finalizer executes without throwing", { 4 | # Start a synchronous backend. 5 | backend <- start_backend(cores = 2, backend_type = "sync") 6 | 7 | # Run a task on the backend. 8 | backend$sapply(1:100, function(x) x) 9 | 10 | # Remove the backend. 11 | rm(backend) 12 | 13 | # Trigger the garbage collection. 14 | gc(verbose = FALSE) 15 | 16 | # Check that no `backend` variable is present. 17 | expect_false(exists("backend")) 18 | }) 19 | 20 | 21 | test_that("'AsyncBackend' finalizer executes without throwing", { 22 | # Define a simple task with non-trivial computation time. 23 | task <- function(x) { 24 | # Sleep a bit. 25 | Sys.sleep(0.001) 26 | 27 | # Return the value. 28 | return(x) 29 | } 30 | 31 | # Ensure forceful stop is disabled. 32 | set_option("stop_forceful", FALSE) 33 | 34 | # #region Test finalizer with a running task. 35 | 36 | # Start an asynchronous backend. 37 | backend <- start_backend(cores = 2, backend_type = "async") 38 | 39 | # Run a task on the backend. 40 | backend$sapply(1:100, task) 41 | 42 | # Remove the backend. 43 | rm(backend) 44 | 45 | # Trigger the garbage collection. 46 | gc(verbose = FALSE) 47 | 48 | # #endregion 49 | 50 | # #region Test finalizer with unread results. 51 | 52 | # Start an asynchronous backend. 53 | backend <- start_backend(cores = 2, backend_type = "async") 54 | 55 | # Run a task on the backend. 56 | backend$sapply(1:100, function(x) x) 57 | 58 | # Block the main session until the task is completed. 59 | block_until_async_task_finished(backend) 60 | 61 | # Remove the backend. 62 | rm(backend) 63 | 64 | # Trigger the garbage collection. 65 | gc(verbose = FALSE) 66 | 67 | # #endregion 68 | 69 | # Enable forceful stop. 70 | set_option("stop_forceful", TRUE) 71 | 72 | # #region Test finalizer with forceful stop while task is running. 73 | 74 | # Start an asynchronous backend. 75 | backend <- start_backend(cores = 2, backend_type = "async") 76 | 77 | # Run a task on the backend. 78 | backend$sapply(1:100, task) 79 | 80 | # Remove the backend. 81 | rm(backend) 82 | 83 | # Trigger the garbage collection. 84 | gc(verbose = FALSE) 85 | 86 | # #endregion 87 | 88 | # #region Test finalizer with forceful stop while task has unread results. 89 | 90 | # Start an asynchronous backend. 91 | backend <- start_backend(cores = 2, backend_type = "async") 92 | 93 | # Run a task on the backend. 94 | backend$sapply(1:100, function(x) x) 95 | 96 | # Block the main session until the task is completed. 97 | block_until_async_task_finished(backend) 98 | 99 | # Remove the backend. 100 | rm(backend) 101 | 102 | # Trigger the garbage collection. 103 | gc(verbose = FALSE) 104 | 105 | # #endregion 106 | 107 | # Check that no `backend` variable is present. 108 | expect_false(exists("backend")) 109 | 110 | # Restore options defaults. 111 | set_default_options() 112 | }) 113 | -------------------------------------------------------------------------------- /man-roxygen/par-lapply.R: -------------------------------------------------------------------------------- 1 | #' @title 2 | #' Run a Task in Parallel 3 | #' 4 | #' @description 5 | #' This function can be used to run a task in parallel. The task is executed in 6 | #' parallel on the specified backend, similar to [`parallel::parLapply()`]. If 7 | #' `backend = NULL`, the task is executed sequentially using [`base::lapply()`]. 8 | #' See the **Details** section for more information on how this function works. 9 | #' 10 | #' @param backend An object of class [`parabar::Backend`] as returned by the 11 | #' [parabar::start_backend()] function. It can also be `NULL` to run the task 12 | #' sequentially via [`base::lapply()`]. The default value is `NULL`. 13 | #' 14 | #' @param x An atomic vector or list to pass to the `fun` function. 15 | #' 16 | #' @param fun A function to apply to each element of `x`. 17 | #' 18 | #' @param ... Additional arguments to pass to the `fun` function. 19 | #' 20 | #' @details 21 | #' This function uses the [`parabar::UserApiConsumer`] class that acts like an 22 | #' interface for the developer API of the [`parabar::parabar`] package. 23 | #' 24 | #' @return 25 | #' A list of the same length as `x` containing the results of the `fun`. The 26 | #' output format resembles that of [`base::lapply()`]. 27 | #' 28 | #' @examples 29 | #' \donttest{ 30 | #' 31 | #' # Define a simple task. 32 | #' task <- function(x) { 33 | #' # Perform computations. 34 | #' Sys.sleep(0.01) 35 | #' 36 | #' # Return the result. 37 | #' return(x + 1) 38 | #' } 39 | #' 40 | #' # Start an asynchronous backend. 41 | #' backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async") 42 | #' 43 | #' # Run a task in parallel. 44 | #' results <- par_lapply(backend, x = 1:300, fun = task) 45 | #' 46 | #' # Disable progress tracking. 47 | #' set_option("progress_track", FALSE) 48 | #' 49 | #' # Run a task in parallel. 50 | #' results <- par_lapply(backend, x = 1:300, fun = task) 51 | #' 52 | #' # Enable progress tracking. 53 | #' set_option("progress_track", TRUE) 54 | #' 55 | #' # Change the progress bar options. 56 | #' configure_bar(type = "modern", format = "[:bar] :percent") 57 | #' 58 | #' # Run a task in parallel. 59 | #' results <- par_lapply(backend, x = 1:300, fun = task) 60 | #' 61 | #' # Stop the backend. 62 | #' stop_backend(backend) 63 | #' 64 | #' # Start a synchronous backend. 65 | #' backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "sync") 66 | #' 67 | #' # Run a task in parallel. 68 | #' results <- par_lapply(backend, x = 1:300, fun = task) 69 | #' 70 | #' # Disable progress tracking to remove the warning that progress is not supported. 71 | #' set_option("progress_track", FALSE) 72 | #' 73 | #' # Run a task in parallel. 74 | #' results <- par_lapply(backend, x = 1:300, fun = task) 75 | #' 76 | #' # Stop the backend. 77 | #' stop_backend(backend) 78 | #' 79 | #' # Run the task using the `base::lapply` (i.e., non-parallel). 80 | #' results <- par_lapply(NULL, x = 1:300, fun = task) 81 | #' 82 | #' } 83 | #' 84 | #' @seealso 85 | #' [parabar::start_backend()], [parabar::peek()], [parabar::export()], 86 | #' [parabar::evaluate()], [parabar::clear()], [parabar::configure_bar()], 87 | #' [parabar::par_sapply()], [parabar::par_apply()], [parabar::stop_backend()], 88 | #' [parabar::set_option()], [parabar::get_option()], [`parabar::Options`], 89 | #' [`parabar::UserApiConsumer`], and [`parabar::BackendService`]. 90 | -------------------------------------------------------------------------------- /man-roxygen/par-sapply.R: -------------------------------------------------------------------------------- 1 | #' @title 2 | #' Run a Task in Parallel 3 | #' 4 | #' @description 5 | #' This function can be used to run a task in parallel. The task is executed in 6 | #' parallel on the specified backend, similar to [`parallel::parSapply()`]. If 7 | #' `backend = NULL`, the task is executed sequentially using [`base::sapply()`]. 8 | #' See the **Details** section for more information on how this function works. 9 | #' 10 | #' @param backend An object of class [`parabar::Backend`] as returned by the 11 | #' [parabar::start_backend()] function. It can also be `NULL` to run the task 12 | #' sequentially via [`base::sapply()`]. The default value is `NULL`. 13 | #' 14 | #' @param x An atomic vector or list to pass to the `fun` function. 15 | #' 16 | #' @param fun A function to apply to each element of `x`. 17 | #' 18 | #' @param ... Additional arguments to pass to the `fun` function. 19 | #' 20 | #' @details 21 | #' This function uses the [`parabar::UserApiConsumer`] class that acts like an 22 | #' interface for the developer API of the [`parabar::parabar`] package. 23 | #' 24 | #' @return 25 | #' A vector of the same length as `x` containing the results of the `fun`. The 26 | #' output format resembles that of [`base::sapply()`]. 27 | #' 28 | #' @examples 29 | #' \donttest{ 30 | #' 31 | #' # Define a simple task. 32 | #' task <- function(x) { 33 | #' # Perform computations. 34 | #' Sys.sleep(0.01) 35 | #' 36 | #' # Return the result. 37 | #' return(x + 1) 38 | #' } 39 | #' 40 | #' # Start an asynchronous backend. 41 | #' backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async") 42 | #' 43 | #' # Run a task in parallel. 44 | #' results <- par_sapply(backend, x = 1:300, fun = task) 45 | #' 46 | #' # Disable progress tracking. 47 | #' set_option("progress_track", FALSE) 48 | #' 49 | #' # Run a task in parallel. 50 | #' results <- par_sapply(backend, x = 1:300, fun = task) 51 | #' 52 | #' # Enable progress tracking. 53 | #' set_option("progress_track", TRUE) 54 | #' 55 | #' # Change the progress bar options. 56 | #' configure_bar(type = "modern", format = "[:bar] :percent") 57 | #' 58 | #' # Run a task in parallel. 59 | #' results <- par_sapply(backend, x = 1:300, fun = task) 60 | #' 61 | #' # Stop the backend. 62 | #' stop_backend(backend) 63 | #' 64 | #' # Start a synchronous backend. 65 | #' backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "sync") 66 | #' 67 | #' # Run a task in parallel. 68 | #' results <- par_sapply(backend, x = 1:300, fun = task) 69 | #' 70 | #' # Disable progress tracking to remove the warning that progress is not supported. 71 | #' set_option("progress_track", FALSE) 72 | #' 73 | #' # Run a task in parallel. 74 | #' results <- par_sapply(backend, x = 1:300, fun = task) 75 | #' 76 | #' # Stop the backend. 77 | #' stop_backend(backend) 78 | #' 79 | #' # Run the task using the `base::sapply` (i.e., non-parallel). 80 | #' results <- par_sapply(NULL, x = 1:300, fun = task) 81 | #' 82 | #' } 83 | #' 84 | #' @seealso 85 | #' [parabar::start_backend()], [parabar::peek()], [parabar::export()], 86 | #' [parabar::evaluate()], [parabar::clear()], [parabar::configure_bar()], 87 | #' [parabar::par_lapply()], [parabar::par_apply()], [parabar::stop_backend()], 88 | #' [parabar::set_option()], [parabar::get_option()], [`parabar::Options`], 89 | #' [`parabar::UserApiConsumer`], and [`parabar::BackendService`]. 90 | -------------------------------------------------------------------------------- /R/Helper.R: -------------------------------------------------------------------------------- 1 | #' @include Options.R 2 | 3 | #' @title 4 | #' Package Helpers 5 | #' 6 | #' @description 7 | #' This class contains static helper methods. 8 | #' 9 | #' @format 10 | #' \describe{ 11 | #' \item{\code{Helper$get_class_name(object)}}{Helper for getting the class of a given object.} 12 | #' \item{\code{Helper$is_of_class(object, class)}}{Check if an object is of a certain class.} 13 | #' \item{\code{Helper$get_option(option)}}{Get package option, or corresponding default value.} 14 | #' \item{\code{Helper$set_option(option, value)}}{Set package option.} 15 | #' \item{\code{Helper$check_object_type(object, expected_type)}}{Check the type of a given object.} 16 | #' \item{\code{Helper$check_array_margins(margins, dimensions)}}{Helper to check array margins for the `BackendService$apply` operation.} 17 | #' } 18 | #' 19 | #' @export 20 | Helper <- R6::R6Class("Helper", 21 | cloneable = FALSE 22 | ) 23 | 24 | # Helper for getting the class of a given instance. 25 | Helper$get_class_name <- function(object) { 26 | return(class(object)[1]) 27 | } 28 | 29 | # Helper to check if object is of certain class. 30 | Helper$is_of_class <- function(object, class) { 31 | return(class(object)[1] == class) 32 | } 33 | 34 | # Get package option, or corresponding default value. 35 | Helper$get_option <- function(option) { 36 | # Get the `Options` instance from the global options, or create a new one. 37 | options <- getOption("parabar", default = Options$new()) 38 | 39 | # If the requested option is unknown. 40 | if (!option %in% ls(options)) { 41 | # Throw an error. 42 | Exception$unknown_package_option(option) 43 | } 44 | 45 | # Return the value. 46 | return(options[[option]]) 47 | } 48 | 49 | # Set package option. 50 | Helper$set_option <- function(option, value) { 51 | # Get the `Options` instance from the global options, or create a new one. 52 | options <- getOption("parabar", default = Options$new()) 53 | 54 | # If the requested option is unknown. 55 | if (!option %in% ls(options)) { 56 | # Throw an error. 57 | Exception$unknown_package_option(option) 58 | } 59 | 60 | # Set the value. 61 | options[[option]] <- value 62 | 63 | # Set the `Options` instance in the global options. 64 | options(parabar = options) 65 | } 66 | 67 | # Helper for performing a type check on a given object. 68 | Helper$check_object_type <- function(object, expected_type) { 69 | # If the object does not inherit from the expected type. 70 | if (!inherits(object, expected_type)) { 71 | # Get object class name. 72 | type <- Helper$get_class_name(object) 73 | 74 | # Throw incorrect type error. 75 | Exception$type_not_assignable(type, expected_type) 76 | } 77 | } 78 | 79 | # Helper for checking the array margins provided for the `apply` operation. 80 | Helper$check_array_margins <- function(margins, dimensions) { 81 | # Conditions to ensure the margins are valid. 82 | violations <- c( 83 | # Ensure all margins are unique. 84 | duplicated(margins), 85 | 86 | # Ensure all margins are within the array dimensions. 87 | margins > length(dimensions) 88 | ) 89 | 90 | # If any violations are found. 91 | if (any(violations)) { 92 | # Throw an error. 93 | Exception$array_margins_not_compatible(margins, dimensions) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /R/BasicBar.R: -------------------------------------------------------------------------------- 1 | #' @include Bar.R 2 | 3 | #' @title 4 | #' BasicBar 5 | #' 6 | #' @description 7 | #' This is a concrete implementation of the abstract class [`parabar::Bar`] 8 | #' using the [`utils::txtProgressBar()`] as engine for the progress bar. 9 | #' 10 | #' @examples 11 | #' # Create a basic bar instance. 12 | #' bar <- BasicBar$new() 13 | #' 14 | #' # Specify the number of ticks to be performed. 15 | #' total <- 100 16 | #' 17 | #' # Create the progress bar. 18 | #' bar$create(total = total, initial = 0) 19 | #' 20 | #' # Use the progress bar. 21 | #' for (i in 1:total) { 22 | #' # Sleep a bit. 23 | #' Sys.sleep(0.02) 24 | #' 25 | #' # Update the progress bar. 26 | #' bar$update(i) 27 | #' } 28 | #' 29 | #' # Terminate the progress bar. 30 | #' bar$terminate() 31 | #' 32 | #' @seealso 33 | #' [`parabar::Bar`], [`parabar::ModernBar`], and [`parabar::BarFactory`]. 34 | #' 35 | #' @export 36 | BasicBar <- R6::R6Class("BasicBar", 37 | inherit = Bar, 38 | 39 | private = list( 40 | # Create bar. 41 | .create = function(total, initial, ...) { 42 | # Create and store the bar object. 43 | private$.bar <- do.call( 44 | utils::txtProgressBar, 45 | utils::modifyList( 46 | list(min = 0, max = total, initial = initial), list(...) 47 | ) 48 | ) 49 | }, 50 | 51 | # Update bar. 52 | .update = function(current) { 53 | # Perform the update. 54 | utils::setTxtProgressBar(private$.bar, current) 55 | }, 56 | 57 | # Terminate the bar. 58 | .terminate = function() { 59 | # Close the bar. 60 | close(private$.bar) 61 | } 62 | ), 63 | 64 | public = list( 65 | #' @description 66 | #' Create a new [`parabar::BasicBar`] object. 67 | #' 68 | #' @return 69 | #' An object of class [`parabar::BasicBar`]. 70 | initialize = function() {}, 71 | 72 | #' @description 73 | #' Create a progress bar. 74 | #' 75 | #' @param total The total number of times the progress bar should tick. 76 | #' 77 | #' @param initial The starting point of the progress bar. 78 | #' 79 | #' @param ... Additional arguments for the bar creation passed to 80 | #' [`utils::txtProgressBar()`]. 81 | #' 82 | #' @return 83 | #' This method returns void. The resulting bar is stored in the private 84 | #' field `.bar`, accessible via the active binding `engine`. Both the 85 | #' private field and the active binding are defined in the super class 86 | #' [`parabar::Bar`]. 87 | create = function(total, initial, ...) { 88 | private$.create(total, initial, ...) 89 | }, 90 | 91 | #' @description 92 | #' Update the progress bar by calling [`utils::setTxtProgressBar()`]. 93 | #' 94 | #' @param current The position the progress bar should be at (e.g., 30 95 | #' out of 100), usually the index in a loop. 96 | update = function(current) { 97 | private$.update(current) 98 | }, 99 | 100 | #' @description 101 | #' Terminate the progress bar by calling [`base::close()`] on the 102 | #' private field `.bar`. 103 | terminate = function() { 104 | private$.terminate() 105 | } 106 | ) 107 | ) 108 | -------------------------------------------------------------------------------- /tests/testthat/test-states.R: -------------------------------------------------------------------------------- 1 | # Test `SessionState` and `TaskState` classes. 2 | 3 | test_that("'SessionState' correctly reports the state given the backend operations", { 4 | # Create a specification. 5 | specification <- Specification$new() 6 | 7 | # Set the number of cores. 8 | specification$set_cores(cores = 2) 9 | 10 | # Pick a cluster type. 11 | cluster_type <- pick_cluster_type(specification$types) 12 | 13 | # Let the specification determine the cluster type. 14 | specification$set_type(type = cluster_type) 15 | 16 | # Create an asynchronous backend object. 17 | backend <- AsyncBackend$new() 18 | 19 | # Start the cluster on the backend. 20 | backend$start(specification) 21 | 22 | # Expect the session to have been started (i.e., due to the wait timeout). 23 | expect_false(backend$session_state$session_is_starting) 24 | 25 | # Expect the session to be idle (i.e., it starts with a wait timeout). 26 | expect_true(backend$session_state$session_is_idle) 27 | 28 | # Run a task on the backend. 29 | backend$sapply(1:100, function(x) { 30 | # Sleep a bit. 31 | Sys.sleep(0.005) 32 | 33 | # Return the value. 34 | return(x) 35 | }) 36 | 37 | # Expect the session to be busy (i.e., it is running a task). 38 | expect_true(backend$session_state$session_is_busy) 39 | 40 | # Wait to read the results into the main session. 41 | results <- backend$get_output(wait = TRUE) 42 | 43 | # Expect the session to be idle (i.e., it has finished with read results). 44 | expect_true(backend$session_state$session_is_idle) 45 | 46 | # Manually close the session. 47 | backend$cluster$close() 48 | 49 | # Expect the session to be finished (i.e., it has been closed). 50 | expect_true(backend$session_state$session_is_finished) 51 | 52 | # Stop the backend. 53 | backend$stop() 54 | }) 55 | 56 | 57 | test_that("'TaskState' correctly reports the state given the backend operations", { 58 | # Create a specification. 59 | specification <- Specification$new() 60 | 61 | # Set the number of cores. 62 | specification$set_cores(cores = 2) 63 | 64 | # Pick a cluster type. 65 | cluster_type <- pick_cluster_type(specification$types) 66 | 67 | # Let the specification determine the cluster type. 68 | specification$set_type(type = cluster_type) 69 | 70 | # Create an asynchronous backend object. 71 | backend <- AsyncBackend$new() 72 | 73 | # Start the cluster on the backend. 74 | backend$start(specification) 75 | 76 | # Expect the task to not have started. 77 | expect_true(backend$task_state$task_not_started) 78 | 79 | # Run a task on the backend. 80 | backend$sapply(1:100, function(x) { 81 | # Sleep a bit. 82 | Sys.sleep(0.005) 83 | 84 | # Return the value. 85 | return(x) 86 | }) 87 | 88 | # Expect the task to be running. 89 | expect_true(backend$task_state$task_is_running) 90 | 91 | # Block the main session until the task is completed. 92 | block_until_async_task_finished(backend) 93 | 94 | # Expect the task to be completed (i.e., with unread results). 95 | expect_true(backend$task_state$task_is_completed) 96 | 97 | # Read the results into the main session. 98 | results <- backend$get_output() 99 | 100 | # Expect the task to not have started. 101 | expect_true(backend$task_state$task_not_started) 102 | 103 | # Stop the backend. 104 | backend$stop() 105 | }) 106 | -------------------------------------------------------------------------------- /man/option.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exports.R 3 | \name{get_option} 4 | \alias{get_option} 5 | \alias{set_option} 6 | \alias{set_default_options} 7 | \title{Get or Set Package Option} 8 | \usage{ 9 | get_option(option) 10 | 11 | set_option(option, value) 12 | 13 | set_default_options() 14 | } 15 | \arguments{ 16 | \item{option}{A character string representing the name of the option to 17 | retrieve or adjust. See the public fields of \code{\link[R6:R6Class]{R6::R6}} class 18 | \code{\link{Options}} for the list of available \code{\link{parabar}} 19 | \code{\link[base:options]{options}}.} 20 | 21 | \item{value}{The value to set the \code{\link[=Options]{option}} to.} 22 | } 23 | \value{ 24 | The \code{\link[=get_option]{get_option()}} function returns the value of the requested 25 | \code{\link[=Options]{option}} present in the \code{\link[base:options]{base::.Options}} list, or its 26 | corresponding default value (i.e., see \code{\link{Options}}). If the 27 | requested \code{\link[=Options]{option}} is not known, an error is thrown. 28 | 29 | The \code{\link[=set_option]{set_option()}} function returns void. It throws an error if the 30 | requested \code{\link[=Options]{option}} to be adjusted is not known. 31 | 32 | The \code{\link[=set_default_options]{set_default_options()}} function returns void. The 33 | \code{\link[base:options]{options}} set can be consulted via the \code{\link[base:options]{base::.Options}} 34 | list. See the \code{\link{Options}} \code{\link[R6:R6Class]{R6::R6}} class for more information on 35 | the default values set by this function. 36 | } 37 | \description{ 38 | The \code{\link[=get_option]{get_option()}} function is a helper for retrieving the value of 39 | \code{\link{parabar}} \code{\link[base:options]{options}}. If the 40 | \code{\link[=Options]{option}} requested is not available in the session 41 | \code{\link[base:options]{base::.Options}} list, the corresponding default value set by the 42 | \code{\link{Options}} \code{\link[R6:R6Class]{R6::R6}} class is returned instead. 43 | 44 | The \code{\link[=set_option]{set_option()}} function is a helper for setting 45 | \code{\link{parabar}} \code{\link[base:options]{options}}. The function adjusts the 46 | fields of the \code{\link{Options}} instance stored in the \code{\link[base:options]{base::.Options}} 47 | list. If no \code{\link{Options}} instance is present in the 48 | \code{\link[base:options]{base::.Options}} list, a new one is created. 49 | 50 | The \code{\link[=set_default_options]{set_default_options()}} function is used to set the default 51 | \code{\link[base:options]{options}} values for the \code{\link{parabar}} package. The 52 | function is automatically called at package load and the entry created can be 53 | retrieved via \code{\link[base:options]{getOption("parabar")}}. Specific package 54 | \code{\link[base:options]{options}} can be retrieved using the helper function 55 | \code{\link[=get_option]{get_option()}}. 56 | } 57 | \examples{ 58 | # Get the status of progress tracking. 59 | get_option("progress_track") 60 | 61 | # Set the status of progress tracking to `FALSE`. 62 | set_option("progress_track", FALSE) 63 | 64 | # Get the status of progress tracking again. 65 | get_option("progress_track") 66 | 67 | # Restore default options. 68 | set_default_options() 69 | 70 | # Get the status of progress tracking yet again. 71 | get_option("progress_track") 72 | 73 | } 74 | \seealso{ 75 | \code{\link{Options}}, \code{\link[=set_default_options]{set_default_options()}}, \code{\link[base:options]{base::options()}}, 76 | and \code{\link[base:options]{base::getOption()}}. 77 | } 78 | -------------------------------------------------------------------------------- /man/par_lapply.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exports.R 3 | \name{par_lapply} 4 | \alias{par_lapply} 5 | \title{Run a Task in Parallel} 6 | \usage{ 7 | par_lapply(backend = NULL, x, fun, ...) 8 | } 9 | \arguments{ 10 | \item{backend}{An object of class \code{\link{Backend}} as returned by the 11 | \code{\link[=start_backend]{start_backend()}} function. It can also be \code{NULL} to run the task 12 | sequentially via \code{\link[base:lapply]{base::lapply()}}. The default value is \code{NULL}.} 13 | 14 | \item{x}{An atomic vector or list to pass to the \code{fun} function.} 15 | 16 | \item{fun}{A function to apply to each element of \code{x}.} 17 | 18 | \item{...}{Additional arguments to pass to the \code{fun} function.} 19 | } 20 | \value{ 21 | A list of the same length as \code{x} containing the results of the \code{fun}. The 22 | output format resembles that of \code{\link[base:lapply]{base::lapply()}}. 23 | } 24 | \description{ 25 | This function can be used to run a task in parallel. The task is executed in 26 | parallel on the specified backend, similar to \code{\link[parallel:clusterApply]{parallel::parLapply()}}. If 27 | \code{backend = NULL}, the task is executed sequentially using \code{\link[base:lapply]{base::lapply()}}. 28 | See the \strong{Details} section for more information on how this function works. 29 | } 30 | \details{ 31 | This function uses the \code{\link{UserApiConsumer}} class that acts like an 32 | interface for the developer API of the \code{\link{parabar}} package. 33 | } 34 | \examples{ 35 | \donttest{ 36 | 37 | # Define a simple task. 38 | task <- function(x) { 39 | # Perform computations. 40 | Sys.sleep(0.01) 41 | 42 | # Return the result. 43 | return(x + 1) 44 | } 45 | 46 | # Start an asynchronous backend. 47 | backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async") 48 | 49 | # Run a task in parallel. 50 | results <- par_lapply(backend, x = 1:300, fun = task) 51 | 52 | # Disable progress tracking. 53 | set_option("progress_track", FALSE) 54 | 55 | # Run a task in parallel. 56 | results <- par_lapply(backend, x = 1:300, fun = task) 57 | 58 | # Enable progress tracking. 59 | set_option("progress_track", TRUE) 60 | 61 | # Change the progress bar options. 62 | configure_bar(type = "modern", format = "[:bar] :percent") 63 | 64 | # Run a task in parallel. 65 | results <- par_lapply(backend, x = 1:300, fun = task) 66 | 67 | # Stop the backend. 68 | stop_backend(backend) 69 | 70 | # Start a synchronous backend. 71 | backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "sync") 72 | 73 | # Run a task in parallel. 74 | results <- par_lapply(backend, x = 1:300, fun = task) 75 | 76 | # Disable progress tracking to remove the warning that progress is not supported. 77 | set_option("progress_track", FALSE) 78 | 79 | # Run a task in parallel. 80 | results <- par_lapply(backend, x = 1:300, fun = task) 81 | 82 | # Stop the backend. 83 | stop_backend(backend) 84 | 85 | # Run the task using the `base::lapply` (i.e., non-parallel). 86 | results <- par_lapply(NULL, x = 1:300, fun = task) 87 | 88 | } 89 | 90 | } 91 | \seealso{ 92 | \code{\link[=start_backend]{start_backend()}}, \code{\link[=peek]{peek()}}, \code{\link[=export]{export()}}, 93 | \code{\link[=evaluate]{evaluate()}}, \code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}}, 94 | \code{\link[=par_sapply]{par_sapply()}}, \code{\link[=par_apply]{par_apply()}}, \code{\link[=stop_backend]{stop_backend()}}, 95 | \code{\link[=set_option]{set_option()}}, \code{\link[=get_option]{get_option()}}, \code{\link{Options}}, 96 | \code{\link{UserApiConsumer}}, and \code{\link{BackendService}}. 97 | } 98 | -------------------------------------------------------------------------------- /man/par_sapply.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exports.R 3 | \name{par_sapply} 4 | \alias{par_sapply} 5 | \title{Run a Task in Parallel} 6 | \usage{ 7 | par_sapply(backend = NULL, x, fun, ...) 8 | } 9 | \arguments{ 10 | \item{backend}{An object of class \code{\link{Backend}} as returned by the 11 | \code{\link[=start_backend]{start_backend()}} function. It can also be \code{NULL} to run the task 12 | sequentially via \code{\link[base:lapply]{base::sapply()}}. The default value is \code{NULL}.} 13 | 14 | \item{x}{An atomic vector or list to pass to the \code{fun} function.} 15 | 16 | \item{fun}{A function to apply to each element of \code{x}.} 17 | 18 | \item{...}{Additional arguments to pass to the \code{fun} function.} 19 | } 20 | \value{ 21 | A vector of the same length as \code{x} containing the results of the \code{fun}. The 22 | output format resembles that of \code{\link[base:lapply]{base::sapply()}}. 23 | } 24 | \description{ 25 | This function can be used to run a task in parallel. The task is executed in 26 | parallel on the specified backend, similar to \code{\link[parallel:clusterApply]{parallel::parSapply()}}. If 27 | \code{backend = NULL}, the task is executed sequentially using \code{\link[base:lapply]{base::sapply()}}. 28 | See the \strong{Details} section for more information on how this function works. 29 | } 30 | \details{ 31 | This function uses the \code{\link{UserApiConsumer}} class that acts like an 32 | interface for the developer API of the \code{\link{parabar}} package. 33 | } 34 | \examples{ 35 | \donttest{ 36 | 37 | # Define a simple task. 38 | task <- function(x) { 39 | # Perform computations. 40 | Sys.sleep(0.01) 41 | 42 | # Return the result. 43 | return(x + 1) 44 | } 45 | 46 | # Start an asynchronous backend. 47 | backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async") 48 | 49 | # Run a task in parallel. 50 | results <- par_sapply(backend, x = 1:300, fun = task) 51 | 52 | # Disable progress tracking. 53 | set_option("progress_track", FALSE) 54 | 55 | # Run a task in parallel. 56 | results <- par_sapply(backend, x = 1:300, fun = task) 57 | 58 | # Enable progress tracking. 59 | set_option("progress_track", TRUE) 60 | 61 | # Change the progress bar options. 62 | configure_bar(type = "modern", format = "[:bar] :percent") 63 | 64 | # Run a task in parallel. 65 | results <- par_sapply(backend, x = 1:300, fun = task) 66 | 67 | # Stop the backend. 68 | stop_backend(backend) 69 | 70 | # Start a synchronous backend. 71 | backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "sync") 72 | 73 | # Run a task in parallel. 74 | results <- par_sapply(backend, x = 1:300, fun = task) 75 | 76 | # Disable progress tracking to remove the warning that progress is not supported. 77 | set_option("progress_track", FALSE) 78 | 79 | # Run a task in parallel. 80 | results <- par_sapply(backend, x = 1:300, fun = task) 81 | 82 | # Stop the backend. 83 | stop_backend(backend) 84 | 85 | # Run the task using the `base::sapply` (i.e., non-parallel). 86 | results <- par_sapply(NULL, x = 1:300, fun = task) 87 | 88 | } 89 | 90 | } 91 | \seealso{ 92 | \code{\link[=start_backend]{start_backend()}}, \code{\link[=peek]{peek()}}, \code{\link[=export]{export()}}, 93 | \code{\link[=evaluate]{evaluate()}}, \code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}}, 94 | \code{\link[=par_lapply]{par_lapply()}}, \code{\link[=par_apply]{par_apply()}}, \code{\link[=stop_backend]{stop_backend()}}, 95 | \code{\link[=set_option]{set_option()}}, \code{\link[=get_option]{get_option()}}, \code{\link{Options}}, 96 | \code{\link{UserApiConsumer}}, and \code{\link{BackendService}}. 97 | } 98 | -------------------------------------------------------------------------------- /R/ModernBar.R: -------------------------------------------------------------------------------- 1 | #' @include Bar.R 2 | 3 | #' @title 4 | #' ModernBar 5 | #' 6 | #' @description 7 | #' This is a concrete implementation of the abstract class [`parabar::Bar`] 8 | #' using the [`progress::progress_bar`] as engine for the progress bar. 9 | #' 10 | #' @examples 11 | #' # Create a modern bar instance. 12 | #' bar <- ModernBar$new() 13 | #' 14 | #' # Specify the number of ticks to be performed. 15 | #' total <- 100 16 | #' 17 | #' # Create the progress bar. 18 | #' bar$create(total = total, initial = 0) 19 | #' 20 | #' # Use the progress bar. 21 | #' for (i in 1:total) { 22 | #' # Sleep a bit. 23 | #' Sys.sleep(0.02) 24 | #' 25 | #' # Update the progress bar. 26 | #' bar$update(i) 27 | #' } 28 | #' 29 | #' # Terminate the progress bar. 30 | #' bar$terminate() 31 | #' 32 | #' @seealso 33 | #' [`parabar::Bar`], [`parabar::BasicBar`], and [`parabar::BarFactory`]. 34 | #' 35 | #' @export 36 | ModernBar <- R6::R6Class("ModernBar", 37 | inherit = Bar, 38 | 39 | private = list( 40 | .total = NULL, 41 | 42 | # Create bar. 43 | .create = function(total, initial, ...) { 44 | # Store the total ticks to be performed. 45 | private$.total <- total 46 | 47 | # Create and store the bar object. 48 | private$.bar <- do.call( 49 | progress::progress_bar$new, 50 | utils::modifyList( 51 | list(total = private$.total), list(...) 52 | ) 53 | ) 54 | 55 | # Perform the initial tick. 56 | private$.bar$tick(initial) 57 | 58 | # Stay silent. 59 | invisible() 60 | }, 61 | 62 | # Update bar. 63 | .update = function(current) { 64 | # Perform the update. 65 | private$.bar$update(current / private$.total) 66 | 67 | # Stay silent. 68 | invisible() 69 | }, 70 | 71 | # Terminate the bar. 72 | .terminate = function() { 73 | # Close the bar. 74 | private$.bar$terminate() 75 | 76 | # Stay silent. 77 | invisible() 78 | } 79 | ), 80 | 81 | public = list( 82 | #' @description 83 | #' Create a new [`parabar::ModernBar`] object. 84 | #' 85 | #' @return 86 | #' An object of class [`parabar::ModernBar`]. 87 | initialize = function() {}, 88 | 89 | #' @description 90 | #' Create a progress bar. 91 | #' 92 | #' @param total The total number of times the progress bar should tick. 93 | #' 94 | #' @param initial The starting point of the progress bar. 95 | #' 96 | #' @param ... Additional arguments for the bar creation passed to 97 | #' [`progress::progress_bar$new()`][`progress::progress_bar`]. 98 | #' 99 | #' @return 100 | #' This method returns void. The resulting bar is stored in the private 101 | #' field `.bar`, accessible via the active binding `engine`. Both the 102 | #' private field and the active binding are defined in the super class 103 | #' [`parabar::Bar`]. 104 | create = function(total, initial, ...) { 105 | private$.create(total, initial, ...) 106 | }, 107 | 108 | #' @description 109 | #' Update the progress bar by calling 110 | #' [`progress::progress_bar$update()`][`progress::progress_bar`]. 111 | #' 112 | #' @param current The position the progress bar should be at (e.g., 30 113 | #' out of 100), usually the index in a loop. 114 | update = function(current) { 115 | private$.update(current) 116 | }, 117 | 118 | #' @description 119 | #' Terminate the progress bar by calling 120 | #' [`progress::progress_bar$terminate()`][`progress::progress_bar`]. 121 | terminate = function() { 122 | private$.terminate() 123 | } 124 | ) 125 | ) 126 | -------------------------------------------------------------------------------- /man/Bar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/Bar.R 3 | \name{Bar} 4 | \alias{Bar} 5 | \title{Bar} 6 | \description{ 7 | This is an abstract class that defines the pure virtual methods a concrete 8 | bar must implement. 9 | } 10 | \details{ 11 | This class cannot be instantiated. It needs to be extended by concrete 12 | subclasses that implement the pure virtual methods. Instances of concrete 13 | backend implementations can be conveniently obtained using the 14 | \code{\link{BarFactory}} class. 15 | } 16 | \seealso{ 17 | \code{\link{BasicBar}}, \code{\link{ModernBar}}, and \code{\link{BarFactory}}. 18 | } 19 | \section{Active bindings}{ 20 | \if{html}{\out{
}} 21 | \describe{ 22 | \item{\code{engine}}{The bar engine.} 23 | } 24 | \if{html}{\out{
}} 25 | } 26 | \section{Methods}{ 27 | \subsection{Public methods}{ 28 | \itemize{ 29 | \item \href{#method-Bar-new}{\code{Bar$new()}} 30 | \item \href{#method-Bar-create}{\code{Bar$create()}} 31 | \item \href{#method-Bar-update}{\code{Bar$update()}} 32 | \item \href{#method-Bar-terminate}{\code{Bar$terminate()}} 33 | \item \href{#method-Bar-clone}{\code{Bar$clone()}} 34 | } 35 | } 36 | \if{html}{\out{
}} 37 | \if{html}{\out{}} 38 | \if{latex}{\out{\hypertarget{method-Bar-new}{}}} 39 | \subsection{Method \code{new()}}{ 40 | Create a new \code{\link{Bar}} object. 41 | \subsection{Usage}{ 42 | \if{html}{\out{
}}\preformatted{Bar$new()}\if{html}{\out{
}} 43 | } 44 | 45 | \subsection{Returns}{ 46 | Instantiating this class will throw an error. 47 | } 48 | } 49 | \if{html}{\out{
}} 50 | \if{html}{\out{}} 51 | \if{latex}{\out{\hypertarget{method-Bar-create}{}}} 52 | \subsection{Method \code{create()}}{ 53 | Create a progress bar. 54 | \subsection{Usage}{ 55 | \if{html}{\out{
}}\preformatted{Bar$create(total, initial, ...)}\if{html}{\out{
}} 56 | } 57 | 58 | \subsection{Arguments}{ 59 | \if{html}{\out{
}} 60 | \describe{ 61 | \item{\code{total}}{The total number of times the progress bar should tick.} 62 | 63 | \item{\code{initial}}{The starting point of the progress bar.} 64 | 65 | \item{\code{...}}{Additional arguments for the bar creation. See the 66 | \strong{Details} section for more information.} 67 | } 68 | \if{html}{\out{
}} 69 | } 70 | \subsection{Details}{ 71 | The optional \code{...} named arguments depend on the specific concrete 72 | implementation (i.e., \code{\link{BasicBar}} or 73 | \code{\link{ModernBar}}). 74 | } 75 | 76 | \subsection{Returns}{ 77 | This method returns void. The resulting bar is stored in the private 78 | field \code{.bar}, accessible via the active binding \code{engine}. 79 | } 80 | } 81 | \if{html}{\out{
}} 82 | \if{html}{\out{}} 83 | \if{latex}{\out{\hypertarget{method-Bar-update}{}}} 84 | \subsection{Method \code{update()}}{ 85 | Update the progress bar. 86 | \subsection{Usage}{ 87 | \if{html}{\out{
}}\preformatted{Bar$update(current)}\if{html}{\out{
}} 88 | } 89 | 90 | \subsection{Arguments}{ 91 | \if{html}{\out{
}} 92 | \describe{ 93 | \item{\code{current}}{The position the progress bar should be at (e.g., 30 94 | out of 100), usually the index in a loop.} 95 | } 96 | \if{html}{\out{
}} 97 | } 98 | } 99 | \if{html}{\out{
}} 100 | \if{html}{\out{}} 101 | \if{latex}{\out{\hypertarget{method-Bar-terminate}{}}} 102 | \subsection{Method \code{terminate()}}{ 103 | Terminate the progress bar. 104 | \subsection{Usage}{ 105 | \if{html}{\out{
}}\preformatted{Bar$terminate()}\if{html}{\out{
}} 106 | } 107 | 108 | } 109 | \if{html}{\out{
}} 110 | \if{html}{\out{}} 111 | \if{latex}{\out{\hypertarget{method-Bar-clone}{}}} 112 | \subsection{Method \code{clone()}}{ 113 | The objects of this class are cloneable with this method. 114 | \subsection{Usage}{ 115 | \if{html}{\out{
}}\preformatted{Bar$clone(deep = FALSE)}\if{html}{\out{
}} 116 | } 117 | 118 | \subsection{Arguments}{ 119 | \if{html}{\out{
}} 120 | \describe{ 121 | \item{\code{deep}}{Whether to make a deep clone.} 122 | } 123 | \if{html}{\out{
}} 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /man-roxygen/par-apply.R: -------------------------------------------------------------------------------- 1 | #' @title 2 | #' Run a Task in Parallel 3 | #' 4 | #' @description 5 | #' This function can be used to run a task in parallel. The task is executed in 6 | #' parallel on the specified backend, similar to [`parallel::parApply()`]. If 7 | #' `backend = NULL`, the task is executed sequentially using [`base::apply()`]. 8 | #' See the **Details** section for more information on how this function works. 9 | #' 10 | #' @param backend An object of class [`parabar::Backend`] as returned by the 11 | #' [parabar::start_backend()] function. It can also be `NULL` to run the task 12 | #' sequentially via [`base::apply()`]. The default value is `NULL`. 13 | #' 14 | #' @param x An array to pass to the `fun` function. 15 | #' 16 | #' @param margin A numeric vector indicating the dimensions of `x` the 17 | #' `fun` function should be applied over. For example, for a matrix, 18 | #' `margin = 1` indicates applying `fun` rows-wise, `margin = 2` 19 | #' indicates applying `fun` columns-wise, and `margin = c(1, 2)` 20 | #' indicates applying `fun` element-wise. Named dimensions are also 21 | #' possible depending on `x`. See [`parallel::parApply()`] and 22 | #' [`base::apply()`] for more details. 23 | #' 24 | #' @param fun A function to apply to `x` according to the `margin`. 25 | #' 26 | #' @param ... Additional arguments to pass to the `fun` function. 27 | #' 28 | #' @details 29 | #' This function uses the [`parabar::UserApiConsumer`] class that acts like an 30 | #' interface for the developer API of the [`parabar::parabar`] package. 31 | #' 32 | #' @return 33 | #' The dimensions of the output vary according to the `margin` argument. Consult 34 | #' the documentation of [`base::apply()`] for a detailed explanation on how the 35 | #' output is structured. 36 | #' 37 | #' @examples 38 | #' \donttest{ 39 | #' 40 | #' # Define a simple task. 41 | #' task <- function(x) { 42 | #' # Perform computations. 43 | #' Sys.sleep(0.01) 44 | #' 45 | #' # Return the result. 46 | #' mean(x) 47 | #' } 48 | #' 49 | #' # Define a matrix for the task. 50 | #' x <- matrix(rnorm(100^2, mean = 10, sd = 0.5), nrow = 100, ncol = 100) 51 | #' 52 | #' # Start an asynchronous backend. 53 | #' backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async") 54 | #' 55 | #' # Run a task in parallel over the rows of `x`. 56 | #' results <- par_apply(backend, x = x, margin = 1, fun = task) 57 | #' 58 | #' # Run a task in parallel over the columns of `x`. 59 | #' results <- par_apply(backend, x = x, margin = 2, fun = task) 60 | #' 61 | #' # The task can also be run over all elements of `x` using `margin = c(1, 2)`. 62 | #' # Improper dimensions will throw an error. 63 | #' try(par_apply(backend, x = x, margin = c(1, 2, 3), fun = task)) 64 | #' 65 | #' # Disable progress tracking. 66 | #' set_option("progress_track", FALSE) 67 | #' 68 | #' # Run a task in parallel. 69 | #' results <- par_apply(backend, x = x, margin = 1, fun = task) 70 | #' 71 | #' # Enable progress tracking. 72 | #' set_option("progress_track", TRUE) 73 | #' 74 | #' # Change the progress bar options. 75 | #' configure_bar(type = "modern", format = "[:bar] :percent") 76 | #' 77 | #' # Run a task in parallel. 78 | #' results <- par_apply(backend, x = x, margin = 1, fun = task) 79 | #' 80 | #' # Stop the backend. 81 | #' stop_backend(backend) 82 | #' 83 | #' # Start a synchronous backend. 84 | #' backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "sync") 85 | #' 86 | #' # Run a task in parallel. 87 | #' results <- par_apply(backend, x = x, margin = 1, fun = task) 88 | #' 89 | #' # Disable progress tracking to remove the warning that progress is not supported. 90 | #' set_option("progress_track", FALSE) 91 | #' 92 | #' # Run a task in parallel. 93 | #' results <- par_apply(backend, x = x, margin = 1, fun = task) 94 | #' 95 | #' # Stop the backend. 96 | #' stop_backend(backend) 97 | #' 98 | #' # Run the task using the `base::lapply` (i.e., non-parallel). 99 | #' results <- par_apply(NULL, x = x, margin = 1, fun = task) 100 | #' 101 | #' } 102 | #' 103 | #' @seealso 104 | #' [parabar::start_backend()], [parabar::peek()], [parabar::export()], 105 | #' [parabar::evaluate()], [parabar::clear()], [parabar::configure_bar()], 106 | #' [parabar::par_sapply()], [parabar::par_lapply()], [parabar::stop_backend()], 107 | #' [parabar::set_option()], [parabar::get_option()], [`parabar::Options`], 108 | #' [`parabar::UserApiConsumer`], and [`parabar::BackendService`]. 109 | -------------------------------------------------------------------------------- /man/Specification.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/Specification.R 3 | \name{Specification} 4 | \alias{Specification} 5 | \title{Specification} 6 | \description{ 7 | This class contains the information required to start a backend. An instance 8 | of this class is used by the \code{start} method of the 9 | \code{\link{BackendService}} interface. 10 | } 11 | \examples{ 12 | # Create a specification object. 13 | specification <- Specification$new() 14 | 15 | # Set the number of cores. 16 | specification$set_cores(cores = 4) 17 | 18 | # Set the cluster type. 19 | specification$set_type(type = "psock") 20 | 21 | # Get the number of cores. 22 | specification$cores 23 | 24 | # Get the cluster type. 25 | specification$type 26 | 27 | # Attempt to set too many cores. 28 | specification$set_cores(cores = 100) 29 | 30 | # Check that the cores were reasonably set. 31 | specification$cores 32 | 33 | # Allow the object to determine the adequate cluster type. 34 | specification$set_type(type = NULL) 35 | 36 | # Check the type determined. 37 | specification$type 38 | 39 | # Attempt to set an invalid cluster type. 40 | specification$set_type(type = "invalid") 41 | 42 | # Check that the type was set to `psock`. 43 | specification$type 44 | 45 | } 46 | \seealso{ 47 | \code{\link{BackendService}}, \code{\link{Backend}}, \code{\link{SyncBackend}}, 48 | and \code{\link{AsyncBackend}}. 49 | } 50 | \section{Active bindings}{ 51 | \if{html}{\out{
}} 52 | \describe{ 53 | \item{\code{cores}}{The number of nodes to use in the cluster creation.} 54 | 55 | \item{\code{type}}{The type of cluster to create.} 56 | 57 | \item{\code{types}}{The supported cluster types.} 58 | } 59 | \if{html}{\out{
}} 60 | } 61 | \section{Methods}{ 62 | \subsection{Public methods}{ 63 | \itemize{ 64 | \item \href{#method-Specification-set_cores}{\code{Specification$set_cores()}} 65 | \item \href{#method-Specification-set_type}{\code{Specification$set_type()}} 66 | \item \href{#method-Specification-clone}{\code{Specification$clone()}} 67 | } 68 | } 69 | \if{html}{\out{
}} 70 | \if{html}{\out{}} 71 | \if{latex}{\out{\hypertarget{method-Specification-set_cores}{}}} 72 | \subsection{Method \code{set_cores()}}{ 73 | Set the number of nodes to use in the cluster. 74 | \subsection{Usage}{ 75 | \if{html}{\out{
}}\preformatted{Specification$set_cores(cores)}\if{html}{\out{
}} 76 | } 77 | 78 | \subsection{Arguments}{ 79 | \if{html}{\out{
}} 80 | \describe{ 81 | \item{\code{cores}}{The number of nodes to use in the cluster.} 82 | } 83 | \if{html}{\out{
}} 84 | } 85 | \subsection{Details}{ 86 | This method also performs a validation of the requested number of 87 | cores, ensuring that the the value lies between \code{1} and 88 | \code{parallel::detectCores() - 1}. 89 | } 90 | 91 | } 92 | \if{html}{\out{
}} 93 | \if{html}{\out{}} 94 | \if{latex}{\out{\hypertarget{method-Specification-set_type}{}}} 95 | \subsection{Method \code{set_type()}}{ 96 | Set the type of cluster to create. 97 | \subsection{Usage}{ 98 | \if{html}{\out{
}}\preformatted{Specification$set_type(type)}\if{html}{\out{
}} 99 | } 100 | 101 | \subsection{Arguments}{ 102 | \if{html}{\out{
}} 103 | \describe{ 104 | \item{\code{type}}{The type of cluster to create. Possible values are 105 | \code{"fork"} and \code{"psock"}. Defaults to \code{"psock"}.} 106 | } 107 | \if{html}{\out{
}} 108 | } 109 | \subsection{Details}{ 110 | If no type is explicitly requested (i.e., \code{type = NULL}), the type is 111 | determined based on the operating system. On Unix-like systems, the 112 | type is set to \code{"fork"}, while on Windows systems, the type is set to 113 | \code{"psock"}. If an unknown type is requested, a warning is issued and 114 | the type is set to \code{"psock"}. 115 | } 116 | 117 | } 118 | \if{html}{\out{
}} 119 | \if{html}{\out{}} 120 | \if{latex}{\out{\hypertarget{method-Specification-clone}{}}} 121 | \subsection{Method \code{clone()}}{ 122 | The objects of this class are cloneable with this method. 123 | \subsection{Usage}{ 124 | \if{html}{\out{
}}\preformatted{Specification$clone(deep = FALSE)}\if{html}{\out{
}} 125 | } 126 | 127 | \subsection{Arguments}{ 128 | \if{html}{\out{
}} 129 | \describe{ 130 | \item{\code{deep}}{Whether to make a deep clone.} 131 | } 132 | \if{html}{\out{
}} 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /man/BasicBar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/BasicBar.R 3 | \name{BasicBar} 4 | \alias{BasicBar} 5 | \title{BasicBar} 6 | \description{ 7 | This is a concrete implementation of the abstract class \code{\link{Bar}} 8 | using the \code{\link[utils:txtProgressBar]{utils::txtProgressBar()}} as engine for the progress bar. 9 | } 10 | \examples{ 11 | # Create a basic bar instance. 12 | bar <- BasicBar$new() 13 | 14 | # Specify the number of ticks to be performed. 15 | total <- 100 16 | 17 | # Create the progress bar. 18 | bar$create(total = total, initial = 0) 19 | 20 | # Use the progress bar. 21 | for (i in 1:total) { 22 | # Sleep a bit. 23 | Sys.sleep(0.02) 24 | 25 | # Update the progress bar. 26 | bar$update(i) 27 | } 28 | 29 | # Terminate the progress bar. 30 | bar$terminate() 31 | 32 | } 33 | \seealso{ 34 | \code{\link{Bar}}, \code{\link{ModernBar}}, and \code{\link{BarFactory}}. 35 | } 36 | \section{Super class}{ 37 | \code{\link[parabar:Bar]{parabar::Bar}} -> \code{BasicBar} 38 | } 39 | \section{Methods}{ 40 | \subsection{Public methods}{ 41 | \itemize{ 42 | \item \href{#method-BasicBar-new}{\code{BasicBar$new()}} 43 | \item \href{#method-BasicBar-create}{\code{BasicBar$create()}} 44 | \item \href{#method-BasicBar-update}{\code{BasicBar$update()}} 45 | \item \href{#method-BasicBar-terminate}{\code{BasicBar$terminate()}} 46 | \item \href{#method-BasicBar-clone}{\code{BasicBar$clone()}} 47 | } 48 | } 49 | \if{html}{\out{
}} 50 | \if{html}{\out{}} 51 | \if{latex}{\out{\hypertarget{method-BasicBar-new}{}}} 52 | \subsection{Method \code{new()}}{ 53 | Create a new \code{\link{BasicBar}} object. 54 | \subsection{Usage}{ 55 | \if{html}{\out{
}}\preformatted{BasicBar$new()}\if{html}{\out{
}} 56 | } 57 | 58 | \subsection{Returns}{ 59 | An object of class \code{\link{BasicBar}}. 60 | } 61 | } 62 | \if{html}{\out{
}} 63 | \if{html}{\out{}} 64 | \if{latex}{\out{\hypertarget{method-BasicBar-create}{}}} 65 | \subsection{Method \code{create()}}{ 66 | Create a progress bar. 67 | \subsection{Usage}{ 68 | \if{html}{\out{
}}\preformatted{BasicBar$create(total, initial, ...)}\if{html}{\out{
}} 69 | } 70 | 71 | \subsection{Arguments}{ 72 | \if{html}{\out{
}} 73 | \describe{ 74 | \item{\code{total}}{The total number of times the progress bar should tick.} 75 | 76 | \item{\code{initial}}{The starting point of the progress bar.} 77 | 78 | \item{\code{...}}{Additional arguments for the bar creation passed to 79 | \code{\link[utils:txtProgressBar]{utils::txtProgressBar()}}.} 80 | } 81 | \if{html}{\out{
}} 82 | } 83 | \subsection{Returns}{ 84 | This method returns void. The resulting bar is stored in the private 85 | field \code{.bar}, accessible via the active binding \code{engine}. Both the 86 | private field and the active binding are defined in the super class 87 | \code{\link{Bar}}. 88 | } 89 | } 90 | \if{html}{\out{
}} 91 | \if{html}{\out{}} 92 | \if{latex}{\out{\hypertarget{method-BasicBar-update}{}}} 93 | \subsection{Method \code{update()}}{ 94 | Update the progress bar by calling \code{\link[utils:txtProgressBar]{utils::setTxtProgressBar()}}. 95 | \subsection{Usage}{ 96 | \if{html}{\out{
}}\preformatted{BasicBar$update(current)}\if{html}{\out{
}} 97 | } 98 | 99 | \subsection{Arguments}{ 100 | \if{html}{\out{
}} 101 | \describe{ 102 | \item{\code{current}}{The position the progress bar should be at (e.g., 30 103 | out of 100), usually the index in a loop.} 104 | } 105 | \if{html}{\out{
}} 106 | } 107 | } 108 | \if{html}{\out{
}} 109 | \if{html}{\out{}} 110 | \if{latex}{\out{\hypertarget{method-BasicBar-terminate}{}}} 111 | \subsection{Method \code{terminate()}}{ 112 | Terminate the progress bar by calling \code{\link[base:connections]{base::close()}} on the 113 | private field \code{.bar}. 114 | \subsection{Usage}{ 115 | \if{html}{\out{
}}\preformatted{BasicBar$terminate()}\if{html}{\out{
}} 116 | } 117 | 118 | } 119 | \if{html}{\out{
}} 120 | \if{html}{\out{}} 121 | \if{latex}{\out{\hypertarget{method-BasicBar-clone}{}}} 122 | \subsection{Method \code{clone()}}{ 123 | The objects of this class are cloneable with this method. 124 | \subsection{Usage}{ 125 | \if{html}{\out{
}}\preformatted{BasicBar$clone(deep = FALSE)}\if{html}{\out{
}} 126 | } 127 | 128 | \subsection{Arguments}{ 129 | \if{html}{\out{
}} 130 | \describe{ 131 | \item{\code{deep}}{Whether to make a deep clone.} 132 | } 133 | \if{html}{\out{
}} 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /tests/testthat/test-async-backend.R: -------------------------------------------------------------------------------- 1 | # Test `AsyncBackend` class. 2 | 3 | test_that("'AsyncBackend' creates and manages clusters correctly", { 4 | # Create a specification. 5 | specification <- Specification$new() 6 | 7 | # Set the number of cores. 8 | specification$set_cores(cores = 2) 9 | 10 | # Pick a cluster type. 11 | cluster_type <- pick_cluster_type(specification$types) 12 | 13 | # Let the specification determine the cluster type. 14 | specification$set_type(type = cluster_type) 15 | 16 | # Create a synchronous backend object. 17 | backend <- AsyncBackend$new() 18 | 19 | # Expect the backend to support progress tracking. 20 | expect_true(backend$supports_progress) 21 | 22 | # Start the cluster on the backend. 23 | backend$start(specification) 24 | 25 | # Expect the cluster to be an object of `callr` class. 26 | expect_true(is(backend$cluster, "r_session")) 27 | 28 | # Expect that the cluster is of correct size. 29 | expect_equal(backend$cluster$run(function() length(cluster)), specification$cores) 30 | 31 | # Expect that the cluster is of correct type. 32 | switch(cluster_type, 33 | "psock" = expect_true(all(tolower(backend$cluster$run(function() summary(cluster))[, 2]) == "socknode")), 34 | "fork" = expect_true(all(tolower(backend$cluster$run(function() summary(cluster))[, 2]) == "forknode")) 35 | ) 36 | 37 | # Test backend states. 38 | tests_set_for_backend_states(backend, specification) 39 | 40 | # Expect error attempting to get the task state for an inactive backend. 41 | expect_error(backend$task_state, as_text(Exception$cluster_not_active())) 42 | 43 | # Expect error attempting to get the session for an inactive backend. 44 | expect_error(backend$session_state, as_text(Exception$cluster_not_active())) 45 | }) 46 | 47 | 48 | test_that("'AsyncBackend' stops busy clusters correctly", { 49 | # Create a specification. 50 | specification <- Specification$new() 51 | 52 | # Set the number of cores. 53 | specification$set_cores(cores = 2) 54 | 55 | # Pick a cluster type. 56 | cluster_type <- pick_cluster_type(specification$types) 57 | 58 | # Let the specification determine the cluster type. 59 | specification$set_type(type = cluster_type) 60 | 61 | # Create a synchronous backend object. 62 | backend <- AsyncBackend$new() 63 | 64 | # Start the cluster on the backend. 65 | backend$start(specification) 66 | 67 | # Disable forceful stopping. 68 | set_option("stop_forceful", FALSE) 69 | 70 | # Make sure the backend is busy. 71 | backend$sapply(1:100, function(x) { 72 | while (TRUE) { } 73 | }) 74 | 75 | # Expect error when stopping a busy backend while a task is running. 76 | expect_error(backend$stop(), as_text(Exception$stop_busy_backend_not_allowed())) 77 | 78 | # Enable forceful stopping. 79 | set_option("stop_forceful", TRUE) 80 | 81 | # Stop the cluster on the backend. 82 | backend$stop() 83 | 84 | # Expect the backend to be inactive. 85 | expect_false(backend$active) 86 | 87 | # Start the cluster on the backend. 88 | backend$start(specification) 89 | 90 | # Disable forceful stopping. 91 | set_option("stop_forceful", FALSE) 92 | 93 | # Make sure the backend has unread results. 94 | backend$sapply(1:100, function(x) x) 95 | 96 | # Block the main session until the task is finished. 97 | block_until_async_task_finished(backend) 98 | 99 | # Expect error when stopping backend with unread task results. 100 | expect_error(backend$stop(), as_text(Exception$stop_busy_backend_not_allowed())) 101 | 102 | # Enable forceful stopping. 103 | set_option("stop_forceful", TRUE) 104 | 105 | # Stop the cluster on the backend. 106 | backend$stop() 107 | 108 | # Expect the backend to be inactive. 109 | expect_false(backend$active) 110 | 111 | # Restore the default options. 112 | set_default_options() 113 | }) 114 | 115 | 116 | test_that("'AsyncBackend' performs operations on the cluster correctly", { 117 | # Create a specification. 118 | specification <- Specification$new() 119 | 120 | # Set the number of cores. 121 | specification$set_cores(cores = 2) 122 | 123 | # Determine the cluster type. 124 | cluster_type <- pick_cluster_type(specification$types) 125 | 126 | # Determine the cluster type automatically. 127 | specification$set_type(type = cluster_type) 128 | 129 | # Create an asynchronous backend object. 130 | backend <- AsyncBackend$new() 131 | 132 | # Perform tests for asynchronous backend operations. 133 | tests_set_for_asynchronous_backend_operations(backend, specification, test_task) 134 | }) 135 | -------------------------------------------------------------------------------- /man/ModernBar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ModernBar.R 3 | \name{ModernBar} 4 | \alias{ModernBar} 5 | \title{ModernBar} 6 | \description{ 7 | This is a concrete implementation of the abstract class \code{\link{Bar}} 8 | using the \code{\link[progress:progress_bar]{progress::progress_bar}} as engine for the progress bar. 9 | } 10 | \examples{ 11 | # Create a modern bar instance. 12 | bar <- ModernBar$new() 13 | 14 | # Specify the number of ticks to be performed. 15 | total <- 100 16 | 17 | # Create the progress bar. 18 | bar$create(total = total, initial = 0) 19 | 20 | # Use the progress bar. 21 | for (i in 1:total) { 22 | # Sleep a bit. 23 | Sys.sleep(0.02) 24 | 25 | # Update the progress bar. 26 | bar$update(i) 27 | } 28 | 29 | # Terminate the progress bar. 30 | bar$terminate() 31 | 32 | } 33 | \seealso{ 34 | \code{\link{Bar}}, \code{\link{BasicBar}}, and \code{\link{BarFactory}}. 35 | } 36 | \section{Super class}{ 37 | \code{\link[parabar:Bar]{parabar::Bar}} -> \code{ModernBar} 38 | } 39 | \section{Methods}{ 40 | \subsection{Public methods}{ 41 | \itemize{ 42 | \item \href{#method-ModernBar-new}{\code{ModernBar$new()}} 43 | \item \href{#method-ModernBar-create}{\code{ModernBar$create()}} 44 | \item \href{#method-ModernBar-update}{\code{ModernBar$update()}} 45 | \item \href{#method-ModernBar-terminate}{\code{ModernBar$terminate()}} 46 | \item \href{#method-ModernBar-clone}{\code{ModernBar$clone()}} 47 | } 48 | } 49 | \if{html}{\out{
}} 50 | \if{html}{\out{}} 51 | \if{latex}{\out{\hypertarget{method-ModernBar-new}{}}} 52 | \subsection{Method \code{new()}}{ 53 | Create a new \code{\link{ModernBar}} object. 54 | \subsection{Usage}{ 55 | \if{html}{\out{
}}\preformatted{ModernBar$new()}\if{html}{\out{
}} 56 | } 57 | 58 | \subsection{Returns}{ 59 | An object of class \code{\link{ModernBar}}. 60 | } 61 | } 62 | \if{html}{\out{
}} 63 | \if{html}{\out{}} 64 | \if{latex}{\out{\hypertarget{method-ModernBar-create}{}}} 65 | \subsection{Method \code{create()}}{ 66 | Create a progress bar. 67 | \subsection{Usage}{ 68 | \if{html}{\out{
}}\preformatted{ModernBar$create(total, initial, ...)}\if{html}{\out{
}} 69 | } 70 | 71 | \subsection{Arguments}{ 72 | \if{html}{\out{
}} 73 | \describe{ 74 | \item{\code{total}}{The total number of times the progress bar should tick.} 75 | 76 | \item{\code{initial}}{The starting point of the progress bar.} 77 | 78 | \item{\code{...}}{Additional arguments for the bar creation passed to 79 | \code{\link[progress:progress_bar]{progress::progress_bar$new()}}.} 80 | } 81 | \if{html}{\out{
}} 82 | } 83 | \subsection{Returns}{ 84 | This method returns void. The resulting bar is stored in the private 85 | field \code{.bar}, accessible via the active binding \code{engine}. Both the 86 | private field and the active binding are defined in the super class 87 | \code{\link{Bar}}. 88 | } 89 | } 90 | \if{html}{\out{
}} 91 | \if{html}{\out{}} 92 | \if{latex}{\out{\hypertarget{method-ModernBar-update}{}}} 93 | \subsection{Method \code{update()}}{ 94 | Update the progress bar by calling 95 | \code{\link[progress:progress_bar]{progress::progress_bar$update()}}. 96 | \subsection{Usage}{ 97 | \if{html}{\out{
}}\preformatted{ModernBar$update(current)}\if{html}{\out{
}} 98 | } 99 | 100 | \subsection{Arguments}{ 101 | \if{html}{\out{
}} 102 | \describe{ 103 | \item{\code{current}}{The position the progress bar should be at (e.g., 30 104 | out of 100), usually the index in a loop.} 105 | } 106 | \if{html}{\out{
}} 107 | } 108 | } 109 | \if{html}{\out{
}} 110 | \if{html}{\out{}} 111 | \if{latex}{\out{\hypertarget{method-ModernBar-terminate}{}}} 112 | \subsection{Method \code{terminate()}}{ 113 | Terminate the progress bar by calling 114 | \code{\link[progress:progress_bar]{progress::progress_bar$terminate()}}. 115 | \subsection{Usage}{ 116 | \if{html}{\out{
}}\preformatted{ModernBar$terminate()}\if{html}{\out{
}} 117 | } 118 | 119 | } 120 | \if{html}{\out{
}} 121 | \if{html}{\out{}} 122 | \if{latex}{\out{\hypertarget{method-ModernBar-clone}{}}} 123 | \subsection{Method \code{clone()}}{ 124 | The objects of this class are cloneable with this method. 125 | \subsection{Usage}{ 126 | \if{html}{\out{
}}\preformatted{ModernBar$clone(deep = FALSE)}\if{html}{\out{
}} 127 | } 128 | 129 | \subsection{Arguments}{ 130 | \if{html}{\out{
}} 131 | \describe{ 132 | \item{\code{deep}}{Whether to make a deep clone.} 133 | } 134 | \if{html}{\out{
}} 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /man/par_apply.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exports.R 3 | \name{par_apply} 4 | \alias{par_apply} 5 | \title{Run a Task in Parallel} 6 | \usage{ 7 | par_apply(backend = NULL, x, margin, fun, ...) 8 | } 9 | \arguments{ 10 | \item{backend}{An object of class \code{\link{Backend}} as returned by the 11 | \code{\link[=start_backend]{start_backend()}} function. It can also be \code{NULL} to run the task 12 | sequentially via \code{\link[base:apply]{base::apply()}}. The default value is \code{NULL}.} 13 | 14 | \item{x}{An array to pass to the \code{fun} function.} 15 | 16 | \item{margin}{A numeric vector indicating the dimensions of \code{x} the 17 | \code{fun} function should be applied over. For example, for a matrix, 18 | \code{margin = 1} indicates applying \code{fun} rows-wise, \code{margin = 2} 19 | indicates applying \code{fun} columns-wise, and \code{margin = c(1, 2)} 20 | indicates applying \code{fun} element-wise. Named dimensions are also 21 | possible depending on \code{x}. See \code{\link[parallel:clusterApply]{parallel::parApply()}} and 22 | \code{\link[base:apply]{base::apply()}} for more details.} 23 | 24 | \item{fun}{A function to apply to \code{x} according to the \code{margin}.} 25 | 26 | \item{...}{Additional arguments to pass to the \code{fun} function.} 27 | } 28 | \value{ 29 | The dimensions of the output vary according to the \code{margin} argument. Consult 30 | the documentation of \code{\link[base:apply]{base::apply()}} for a detailed explanation on how the 31 | output is structured. 32 | } 33 | \description{ 34 | This function can be used to run a task in parallel. The task is executed in 35 | parallel on the specified backend, similar to \code{\link[parallel:clusterApply]{parallel::parApply()}}. If 36 | \code{backend = NULL}, the task is executed sequentially using \code{\link[base:apply]{base::apply()}}. 37 | See the \strong{Details} section for more information on how this function works. 38 | } 39 | \details{ 40 | This function uses the \code{\link{UserApiConsumer}} class that acts like an 41 | interface for the developer API of the \code{\link{parabar}} package. 42 | } 43 | \examples{ 44 | \donttest{ 45 | 46 | # Define a simple task. 47 | task <- function(x) { 48 | # Perform computations. 49 | Sys.sleep(0.01) 50 | 51 | # Return the result. 52 | mean(x) 53 | } 54 | 55 | # Define a matrix for the task. 56 | x <- matrix(rnorm(100^2, mean = 10, sd = 0.5), nrow = 100, ncol = 100) 57 | 58 | # Start an asynchronous backend. 59 | backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async") 60 | 61 | # Run a task in parallel over the rows of `x`. 62 | results <- par_apply(backend, x = x, margin = 1, fun = task) 63 | 64 | # Run a task in parallel over the columns of `x`. 65 | results <- par_apply(backend, x = x, margin = 2, fun = task) 66 | 67 | # The task can also be run over all elements of `x` using `margin = c(1, 2)`. 68 | # Improper dimensions will throw an error. 69 | try(par_apply(backend, x = x, margin = c(1, 2, 3), fun = task)) 70 | 71 | # Disable progress tracking. 72 | set_option("progress_track", FALSE) 73 | 74 | # Run a task in parallel. 75 | results <- par_apply(backend, x = x, margin = 1, fun = task) 76 | 77 | # Enable progress tracking. 78 | set_option("progress_track", TRUE) 79 | 80 | # Change the progress bar options. 81 | configure_bar(type = "modern", format = "[:bar] :percent") 82 | 83 | # Run a task in parallel. 84 | results <- par_apply(backend, x = x, margin = 1, fun = task) 85 | 86 | # Stop the backend. 87 | stop_backend(backend) 88 | 89 | # Start a synchronous backend. 90 | backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "sync") 91 | 92 | # Run a task in parallel. 93 | results <- par_apply(backend, x = x, margin = 1, fun = task) 94 | 95 | # Disable progress tracking to remove the warning that progress is not supported. 96 | set_option("progress_track", FALSE) 97 | 98 | # Run a task in parallel. 99 | results <- par_apply(backend, x = x, margin = 1, fun = task) 100 | 101 | # Stop the backend. 102 | stop_backend(backend) 103 | 104 | # Run the task using the `base::lapply` (i.e., non-parallel). 105 | results <- par_apply(NULL, x = x, margin = 1, fun = task) 106 | 107 | } 108 | 109 | } 110 | \seealso{ 111 | \code{\link[=start_backend]{start_backend()}}, \code{\link[=peek]{peek()}}, \code{\link[=export]{export()}}, 112 | \code{\link[=evaluate]{evaluate()}}, \code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}}, 113 | \code{\link[=par_sapply]{par_sapply()}}, \code{\link[=par_lapply]{par_lapply()}}, \code{\link[=stop_backend]{stop_backend()}}, 114 | \code{\link[=set_option]{set_option()}}, \code{\link[=get_option]{get_option()}}, \code{\link{Options}}, 115 | \code{\link{UserApiConsumer}}, and \code{\link{BackendService}}. 116 | } 117 | -------------------------------------------------------------------------------- /man/Backend.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/Backend.R 3 | \name{Backend} 4 | \alias{Backend} 5 | \title{Backend} 6 | \description{ 7 | This is an abstract class that serves as a base class for all concrete 8 | backend implementations. It defines the common properties that all concrete 9 | backends require. 10 | } 11 | \details{ 12 | This class cannot be instantiated. It needs to be extended by concrete 13 | subclasses that implement the pure virtual methods. Instances of concrete 14 | backend implementations can be conveniently obtained using the 15 | \code{\link{BackendFactory}} class. 16 | } 17 | \seealso{ 18 | \code{\link{BackendService}}, \code{\link{SyncBackend}}, 19 | \code{\link{AsyncBackend}}, \code{\link{BackendFactory}}, and 20 | \code{\link{Context}}. 21 | } 22 | \section{Super class}{ 23 | \code{\link[parabar:BackendService]{parabar::BackendService}} -> \code{Backend} 24 | } 25 | \section{Active bindings}{ 26 | \if{html}{\out{
}} 27 | \describe{ 28 | \item{\code{cluster}}{The cluster object used by the backend. For 29 | \code{\link{SyncBackend}} objects, this is a cluster object created by 30 | \code{\link[parallel:makeCluster]{parallel::makeCluster()}}. For \code{\link{AsyncBackend}} objects, 31 | this is a permanent \code{R} session created by \code{\link[callr:r_session]{callr::r_session}} that 32 | contains the \code{\link[parallel:makeCluster]{parallel::makeCluster()}} cluster object.} 33 | 34 | \item{\code{supports_progress}}{A boolean value indicating whether the 35 | backend implementation supports progress tracking.} 36 | 37 | \item{\code{active}}{A boolean value indicating whether the backend 38 | implementation has an active cluster.} 39 | } 40 | \if{html}{\out{
}} 41 | } 42 | \section{Methods}{ 43 | \subsection{Public methods}{ 44 | \itemize{ 45 | \item \href{#method-Backend-new}{\code{Backend$new()}} 46 | \item \href{#method-Backend-clone}{\code{Backend$clone()}} 47 | } 48 | } 49 | \if{html}{\out{ 50 |
Inherited methods 51 | 63 |
64 | }} 65 | \if{html}{\out{
}} 66 | \if{html}{\out{}} 67 | \if{latex}{\out{\hypertarget{method-Backend-new}{}}} 68 | \subsection{Method \code{new()}}{ 69 | Create a new \code{\link{Backend}} object. 70 | \subsection{Usage}{ 71 | \if{html}{\out{
}}\preformatted{Backend$new()}\if{html}{\out{
}} 72 | } 73 | 74 | \subsection{Returns}{ 75 | Instantiating this class will throw an error. 76 | } 77 | } 78 | \if{html}{\out{
}} 79 | \if{html}{\out{}} 80 | \if{latex}{\out{\hypertarget{method-Backend-clone}{}}} 81 | \subsection{Method \code{clone()}}{ 82 | The objects of this class are cloneable with this method. 83 | \subsection{Usage}{ 84 | \if{html}{\out{
}}\preformatted{Backend$clone(deep = FALSE)}\if{html}{\out{
}} 85 | } 86 | 87 | \subsection{Arguments}{ 88 | \if{html}{\out{
}} 89 | \describe{ 90 | \item{\code{deep}}{Whether to make a deep clone.} 91 | } 92 | \if{html}{\out{
}} 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /man-roxygen/start-backend.R: -------------------------------------------------------------------------------- 1 | #' @title 2 | #' Start a Backend 3 | #' 4 | #' @description 5 | #' This function can be used to start a backend. Check out the **Details** 6 | #' section for more information. 7 | #' 8 | #' @param cores A positive integer representing the number of cores to use 9 | #' (i.e., the number of processes to start). This value must be between `2` and 10 | #' `parallel::detectCores() - 1`. 11 | #' 12 | #' @param cluster_type A character string representing the type of cluster to 13 | #' create. Possible values are `"fork"` and `"psock"`. Defaults to `"psock"`. 14 | #' See the section **Cluster Type** for more information. 15 | #' 16 | #' @param backend_type A character string representing the type of backend to 17 | #' create. Possible values are `"sync"` and `"async"`. Defaults to `"async"`. 18 | #' See the section **Backend Type** for more information. 19 | #' 20 | #' @details 21 | #' This function is a convenience wrapper around the lower-lever API of 22 | #' [`parabar::parabar`] aimed at developers. More specifically, this function 23 | #' uses the [`parabar::Specification`] class to create a specification object, 24 | #' and the [`parabar::BackendFactory`] class to create a [`parabar::Backend`] 25 | #' instance based on the specification object. 26 | #' 27 | #' @section Cluster Type: 28 | #' The cluster type determines the type of cluster to create. The requested 29 | #' value is validated and passed to the `type` argument of the 30 | #' [parallel::makeCluster()] function. The following table lists the possible 31 | #' values and their corresponding description. 32 | #' 33 | #' | **Cluster** | **Description** | 34 | #' | :---------- | :------------------------- | 35 | #' | `"fork"` | For Unix-based systems. | 36 | #' | `"psock"` | For Windows-based systems. | 37 | #' 38 | #' @section Backend Type: 39 | #' The backend type determines the type of backend to create. The requested 40 | #' value is passed to the [`parabar::BackendFactory`] class, which returns a 41 | #' [`parabar::Backend`] instance of the desired type. The following table lists 42 | #' the possible backend types and their corresponding description. 43 | #' 44 | #' | **Backend** | **Description** | **Implementation** | **Progress** | 45 | #' | :---------- | :----------------------- | :------------------------ | :----------: | 46 | #' | `"sync"` | A synchronous backend. | [`parabar::SyncBackend`] | no | 47 | #' | `"async"` | An asynchronous backend. | [`parabar::AsyncBackend`] | yes | 48 | #' 49 | #' In a nutshell, the difference between the two backend types is that for the 50 | #' synchronous backend the cluster is created in the main process, while for the 51 | #' asynchronous backend the cluster is created in a backend `R` process using 52 | #' [`callr::r_session`]. Therefore, the synchronous backend is blocking the main 53 | #' process during task execution, while the asynchronous backend is 54 | #' non-blocking. Check out the implementations listed in the table above for 55 | #' more information. All concrete implementations extend the 56 | #' [`parabar::Backend`] abstract class and implement the 57 | #' [`parabar::BackendService`] interface. 58 | #' 59 | #' @return 60 | #' A [`parabar::Backend`] instance that can be used to parallelize computations. 61 | #' The methods available on the [`parabar::Backend`] instance are defined by the 62 | #' [`parabar::BackendService`] interface. 63 | #' 64 | #' @examples 65 | #' # Create an asynchronous backend. 66 | #' backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async") 67 | #' 68 | #' # Check that the backend is active. 69 | #' backend$active 70 | #' 71 | #' # Check if there is anything on the backend. 72 | #' peek(backend) 73 | #' 74 | #' # Create a dummy variable. 75 | #' name <- "parabar" 76 | #' 77 | #' # Export the `name` variable in the current environment to the backend. 78 | #' export(backend, "name", environment()) 79 | #' 80 | #' # Remove the dummy variable from the current environment. 81 | #' rm(name) 82 | #' 83 | #' # Check the backend to see that the variable has been exported. 84 | #' peek(backend) 85 | #' 86 | #' # Run an expression on the backend. 87 | #' # Note that the symbols in the expression are resolved on the backend. 88 | #' evaluate(backend, { 89 | #' # Print the name. 90 | #' print(paste0("Hello, ", name, "!")) 91 | #' }) 92 | #' 93 | #' # Clear the backend. 94 | #' clear(backend) 95 | #' 96 | #' # Check that there is nothing on the backend. 97 | #' peek(backend) 98 | #' 99 | #' # Use a basic progress bar (i.e., see `parabar::Bar`). 100 | #' configure_bar(type = "basic", style = 3) 101 | #' 102 | #' # Run a task in parallel (i.e., approx. 1.25 seconds). 103 | #' output <- par_sapply(backend, x = 1:10, fun = function(x) { 104 | #' # Sleep a bit. 105 | #' Sys.sleep(0.25) 106 | #' 107 | #' # Compute and return. 108 | #' return(x + 1) 109 | #' }) 110 | #' 111 | #' # Print the output. 112 | #' print(output) 113 | #' 114 | #' # Stop the backend. 115 | #' stop_backend(backend) 116 | #' 117 | #' # Check that the backend is not active. 118 | #' backend$active 119 | #' 120 | #' @seealso 121 | #' [parabar::peek()], [parabar::export()], [parabar::evaluate()], 122 | #' [parabar::clear()], [parabar::configure_bar()], [parabar::par_sapply()], 123 | #' [parabar::par_lapply()], [parabar::par_apply()], [parabar::stop_backend()], 124 | #' and [`parabar::BackendService`]. 125 | -------------------------------------------------------------------------------- /man/Options.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/Options.R 3 | \name{Options} 4 | \alias{Options} 5 | \title{Class for Package Options} 6 | \description{ 7 | This class holds public fields that represent the package 8 | \code{\link[base:options]{options}} used to configure the default behavior of the 9 | functionality \code{\link{parabar}} provides. 10 | } 11 | \details{ 12 | An instance of this class is automatically created and stored in the session 13 | \code{\link[base:options]{base::.Options}} at load time. This instance can be accessed and changed 14 | via \code{\link[base:options]{getOption("parabar")}}. Specific package 15 | \code{\link[base:options]{options}} can be retrieved using the helper function 16 | \code{\link[=get_option]{get_option()}}. 17 | } 18 | \examples{ 19 | # Set the default package options (i.e., automatically set at load time). 20 | set_default_options() 21 | 22 | # First, get the options instance from the session options. 23 | parabar <- getOption("parabar") 24 | 25 | # Then, disable progress tracking. 26 | parabar$progress_track <- FALSE 27 | 28 | # Check that the change was applied (i.e., `progress_track: FALSE`). 29 | getOption("parabar") 30 | 31 | # To restore defaults, set the default options again. 32 | set_default_options() 33 | 34 | # Check that the change was applied (i.e., `progress_track: TRUE`). 35 | getOption("parabar") 36 | 37 | # We can also use the built-in helpers to get and set options more conveniently. 38 | 39 | # Get the progress tracking option. 40 | get_option("progress_track") 41 | 42 | # Set the progress tracking option to `FALSE`. 43 | set_option("progress_track", FALSE) 44 | 45 | # Check that the change was applied (i.e., `progress_track: FALSE`). 46 | get_option("progress_track") 47 | 48 | # Get a temporary file for logging the progress. 49 | get_option("progress_log_path") 50 | 51 | # Fix the logging file path. 52 | set_option("progress_log_path", "./progress.log") 53 | 54 | # Check that the logging path change was applied. 55 | get_option("progress_log_path") 56 | 57 | # Restore the logging path to the default behavior. 58 | set_option("progress_log_path", NULL) 59 | 60 | # Check that the logging path change was applied. 61 | get_option("progress_log_path") 62 | 63 | # Restore the defaults. 64 | set_default_options() 65 | 66 | } 67 | \seealso{ 68 | \code{\link[=get_option]{get_option()}}, \code{\link[=set_option]{set_option()}}, and 69 | \code{\link[=set_default_options]{set_default_options()}}. 70 | } 71 | \section{Public fields}{ 72 | \if{html}{\out{
}} 73 | \describe{ 74 | \item{\code{progress_track}}{A logical value indicating whether progress 75 | tracking should be enabled (i.e., \code{TRUE}) or disabled (i.e., 76 | \code{FALSE}) globally for compatible backends. The default value is 77 | \code{TRUE}.} 78 | 79 | \item{\code{progress_timeout}}{A numeric value indicating the timeout (i.e., 80 | in seconds) between subsequent checks of the log file for new 81 | progress records. The default value is \code{0.001}.} 82 | 83 | \item{\code{progress_wait}}{A numeric value indicating the approximate 84 | duration (i.e., in seconds) to wait between progress bar updates 85 | before checking if the task has finished (i.e., possibly with an 86 | error). The default value is \code{0.1}.} 87 | 88 | \item{\code{progress_bar_type}}{A character string indicating the default 89 | bar type to use with compatible backends. Possible values are 90 | \code{"modern"} (the default) or \code{"basic"}.} 91 | 92 | \item{\code{progress_bar_config}}{A list of lists containing the default bar 93 | configuration for each supported bar engine. Elements of these lists 94 | represent arguments for the corresponding bar engines. Currently, the 95 | supported bar engines are: 96 | \itemize{ 97 | \item \code{modern}: The \code{\link[progress:progress_bar]{progress::progress_bar}} engine, with the following 98 | default configuration: 99 | \itemize{ 100 | \item \code{show_after = 0} 101 | \item \code{format = "> completed :current out of :total tasks [:percent] [:elapsed]"} 102 | } 103 | \item \code{basic}: The \code{\link[utils:txtProgressBar]{utils::txtProgressBar}} engine, with no default 104 | configuration. 105 | }} 106 | 107 | \item{\code{stop_forceful}}{A logical value indicating whether to allow 108 | stopping an asynchronous backend forcefully (i.e., \code{TRUE}), or not 109 | (i.e., \code{FALSE}). When stopping forcefully, the backend is terminated 110 | without waiting for a running tasks to finish or for the results to 111 | be read into the main \code{R} session. The default value is \code{FALSE}.} 112 | } 113 | \if{html}{\out{
}} 114 | } 115 | \section{Active bindings}{ 116 | \if{html}{\out{
}} 117 | \describe{ 118 | \item{\code{progress_log_path}}{A character string indicating the path to 119 | the log file where to track the execution progress of a running task. 120 | The default value is a temporary file generated by 121 | \code{\link[base:tempfile]{base::tempfile()}}. Calling this active binding repeatedly will 122 | yield different temporary file paths. Fixing the path to a specific 123 | value is possible by setting this active binding to a character 124 | string representing the desired path. Setting this active binding to 125 | \code{NULL} will reset it to the default value (i.e., yielding different 126 | temporary file paths).} 127 | } 128 | \if{html}{\out{
}} 129 | } 130 | -------------------------------------------------------------------------------- /man/start_backend.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exports.R 3 | \name{start_backend} 4 | \alias{start_backend} 5 | \title{Start a Backend} 6 | \usage{ 7 | start_backend(cores, cluster_type = "psock", backend_type = "async") 8 | } 9 | \arguments{ 10 | \item{cores}{A positive integer representing the number of cores to use 11 | (i.e., the number of processes to start). This value must be between \code{2} and 12 | \code{parallel::detectCores() - 1}.} 13 | 14 | \item{cluster_type}{A character string representing the type of cluster to 15 | create. Possible values are \code{"fork"} and \code{"psock"}. Defaults to \code{"psock"}. 16 | See the section \strong{Cluster Type} for more information.} 17 | 18 | \item{backend_type}{A character string representing the type of backend to 19 | create. Possible values are \code{"sync"} and \code{"async"}. Defaults to \code{"async"}. 20 | See the section \strong{Backend Type} for more information.} 21 | } 22 | \value{ 23 | A \code{\link{Backend}} instance that can be used to parallelize computations. 24 | The methods available on the \code{\link{Backend}} instance are defined by the 25 | \code{\link{BackendService}} interface. 26 | } 27 | \description{ 28 | This function can be used to start a backend. Check out the \strong{Details} 29 | section for more information. 30 | } 31 | \details{ 32 | This function is a convenience wrapper around the lower-lever API of 33 | \code{\link{parabar}} aimed at developers. More specifically, this function 34 | uses the \code{\link{Specification}} class to create a specification object, 35 | and the \code{\link{BackendFactory}} class to create a \code{\link{Backend}} 36 | instance based on the specification object. 37 | } 38 | \section{Cluster Type}{ 39 | 40 | The cluster type determines the type of cluster to create. The requested 41 | value is validated and passed to the \code{type} argument of the 42 | \code{\link[parallel:makeCluster]{parallel::makeCluster()}} function. The following table lists the possible 43 | values and their corresponding description.\tabular{ll}{ 44 | \strong{Cluster} \tab \strong{Description} \cr 45 | \code{"fork"} \tab For Unix-based systems. \cr 46 | \code{"psock"} \tab For Windows-based systems. \cr 47 | } 48 | } 49 | 50 | \section{Backend Type}{ 51 | 52 | The backend type determines the type of backend to create. The requested 53 | value is passed to the \code{\link{BackendFactory}} class, which returns a 54 | \code{\link{Backend}} instance of the desired type. The following table lists 55 | the possible backend types and their corresponding description.\tabular{lllc}{ 56 | \strong{Backend} \tab \strong{Description} \tab \strong{Implementation} \tab \strong{Progress} \cr 57 | \code{"sync"} \tab A synchronous backend. \tab \code{\link{SyncBackend}} \tab no \cr 58 | \code{"async"} \tab An asynchronous backend. \tab \code{\link{AsyncBackend}} \tab yes \cr 59 | } 60 | 61 | 62 | In a nutshell, the difference between the two backend types is that for the 63 | synchronous backend the cluster is created in the main process, while for the 64 | asynchronous backend the cluster is created in a backend \code{R} process using 65 | \code{\link[callr:r_session]{callr::r_session}}. Therefore, the synchronous backend is blocking the main 66 | process during task execution, while the asynchronous backend is 67 | non-blocking. Check out the implementations listed in the table above for 68 | more information. All concrete implementations extend the 69 | \code{\link{Backend}} abstract class and implement the 70 | \code{\link{BackendService}} interface. 71 | } 72 | 73 | \examples{ 74 | # Create an asynchronous backend. 75 | backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async") 76 | 77 | # Check that the backend is active. 78 | backend$active 79 | 80 | # Check if there is anything on the backend. 81 | peek(backend) 82 | 83 | # Create a dummy variable. 84 | name <- "parabar" 85 | 86 | # Export the `name` variable in the current environment to the backend. 87 | export(backend, "name", environment()) 88 | 89 | # Remove the dummy variable from the current environment. 90 | rm(name) 91 | 92 | # Check the backend to see that the variable has been exported. 93 | peek(backend) 94 | 95 | # Run an expression on the backend. 96 | # Note that the symbols in the expression are resolved on the backend. 97 | evaluate(backend, { 98 | # Print the name. 99 | print(paste0("Hello, ", name, "!")) 100 | }) 101 | 102 | # Clear the backend. 103 | clear(backend) 104 | 105 | # Check that there is nothing on the backend. 106 | peek(backend) 107 | 108 | # Use a basic progress bar (i.e., see `parabar::Bar`). 109 | configure_bar(type = "basic", style = 3) 110 | 111 | # Run a task in parallel (i.e., approx. 1.25 seconds). 112 | output <- par_sapply(backend, x = 1:10, fun = function(x) { 113 | # Sleep a bit. 114 | Sys.sleep(0.25) 115 | 116 | # Compute and return. 117 | return(x + 1) 118 | }) 119 | 120 | # Print the output. 121 | print(output) 122 | 123 | # Stop the backend. 124 | stop_backend(backend) 125 | 126 | # Check that the backend is not active. 127 | backend$active 128 | 129 | } 130 | \seealso{ 131 | \code{\link[=peek]{peek()}}, \code{\link[=export]{export()}}, \code{\link[=evaluate]{evaluate()}}, 132 | \code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}}, \code{\link[=par_sapply]{par_sapply()}}, 133 | \code{\link[=par_lapply]{par_lapply()}}, \code{\link[=par_apply]{par_apply()}}, \code{\link[=stop_backend]{stop_backend()}}, 134 | and \code{\link{BackendService}}. 135 | } 136 | -------------------------------------------------------------------------------- /man/SessionState.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/SessionState.R 3 | \name{SessionState} 4 | \alias{SessionState} 5 | \title{SessionState} 6 | \description{ 7 | This class holds the state of a background \code{\link[callr:r_session]{session}} 8 | used by an asynchronous backend (i.e., \code{\link{AsyncBackend}}). See the 9 | \strong{Details} section for more information. 10 | } 11 | \details{ 12 | The session state is useful to check if an asynchronous backend is ready for 13 | certain operations. A session can only be in one of the following four states 14 | at a time: 15 | \itemize{ 16 | \item \code{session_is_starting}: When \code{TRUE}, it indicates that the session is 17 | starting. 18 | \item \code{session_is_idle}: When \code{TRUE}, it indicates that the session is idle and 19 | ready to execute operations. 20 | \item \code{session_is_busy}: When \code{TRUE}, it indicates that the session is busy 21 | (i.e., see the \code{\link{TaskState}} class for more information about a 22 | task's state). 23 | \item \code{session_is_finished}: When \code{TRUE}, it indicates that the session is closed 24 | and no longer available for operations. 25 | } 26 | } 27 | \examples{ 28 | # Handy function to print the session states all at once. 29 | check_state <- function(session) { 30 | # Create a session object and determine its state. 31 | session_state <- SessionState$new(session) 32 | 33 | # Print the state. 34 | cat( 35 | "Session is starting: ", session_state$session_is_starting, "\n", 36 | "Session is idle: ", session_state$session_is_idle, "\n", 37 | "Session is busy: ", session_state$session_is_busy, "\n", 38 | "Session is finished: ", session_state$session_is_finished, "\n", 39 | sep = "" 40 | ) 41 | } 42 | 43 | # Create a specification object. 44 | specification <- Specification$new() 45 | 46 | # Set the number of cores. 47 | specification$set_cores(cores = 2) 48 | 49 | # Set the cluster type. 50 | specification$set_type(type = "psock") 51 | 52 | # Create an asynchronous backend object. 53 | backend <- AsyncBackend$new() 54 | 55 | # Start the cluster on the backend. 56 | backend$start(specification) 57 | 58 | # Check that the session is idle. 59 | check_state(backend$cluster) 60 | 61 | { 62 | # Run a task in parallel (i.e., approx. 0.25 seconds). 63 | backend$sapply( 64 | x = 1:10, 65 | fun = function(x) { 66 | # Sleep a bit. 67 | Sys.sleep(0.05) 68 | 69 | # Compute something. 70 | output <- x + 1 71 | 72 | # Return the result. 73 | return(output) 74 | } 75 | ) 76 | 77 | # And immediately check that the session is busy. 78 | check_state(backend$cluster) 79 | } 80 | 81 | # Get the output and wait for the task to complete. 82 | output <- backend$get_output(wait = TRUE) 83 | 84 | # Check that the session is idle again. 85 | check_state(backend$cluster) 86 | 87 | # Manually close the session. 88 | backend$cluster$close() 89 | 90 | # Check that the session is finished. 91 | check_state(backend$cluster) 92 | 93 | # Stop the backend. 94 | backend$stop() 95 | 96 | } 97 | \seealso{ 98 | \code{\link{TaskState}}, \code{\link{AsyncBackend}} and 99 | \code{\link{ProgressTrackingContext}}. 100 | } 101 | \section{Active bindings}{ 102 | \if{html}{\out{
}} 103 | \describe{ 104 | \item{\code{session_is_starting}}{A logical value indicating whether the 105 | session is starting.} 106 | 107 | \item{\code{session_is_idle}}{A logical value indicating whether the session 108 | is idle and ready to execute operations.} 109 | 110 | \item{\code{session_is_busy}}{A logical value indicating whether the session 111 | is busy.} 112 | 113 | \item{\code{session_is_finished}}{A logical value indicating whether the 114 | session is closed and no longer available for operations.} 115 | } 116 | \if{html}{\out{
}} 117 | } 118 | \section{Methods}{ 119 | \subsection{Public methods}{ 120 | \itemize{ 121 | \item \href{#method-SessionState-new}{\code{SessionState$new()}} 122 | \item \href{#method-SessionState-clone}{\code{SessionState$clone()}} 123 | } 124 | } 125 | \if{html}{\out{
}} 126 | \if{html}{\out{}} 127 | \if{latex}{\out{\hypertarget{method-SessionState-new}{}}} 128 | \subsection{Method \code{new()}}{ 129 | Create a new \code{\link{SessionState}} object and determine the state 130 | of a given background \code{\link[callr:r_session]{session}}. 131 | \subsection{Usage}{ 132 | \if{html}{\out{
}}\preformatted{SessionState$new(session)}\if{html}{\out{
}} 133 | } 134 | 135 | \subsection{Arguments}{ 136 | \if{html}{\out{
}} 137 | \describe{ 138 | \item{\code{session}}{A \code{\link[callr:r_session]{callr::r_session}} object.} 139 | } 140 | \if{html}{\out{
}} 141 | } 142 | \subsection{Returns}{ 143 | An object of class \code{\link{SessionState}}. 144 | } 145 | } 146 | \if{html}{\out{
}} 147 | \if{html}{\out{}} 148 | \if{latex}{\out{\hypertarget{method-SessionState-clone}{}}} 149 | \subsection{Method \code{clone()}}{ 150 | The objects of this class are cloneable with this method. 151 | \subsection{Usage}{ 152 | \if{html}{\out{
}}\preformatted{SessionState$clone(deep = FALSE)}\if{html}{\out{
}} 153 | } 154 | 155 | \subsection{Arguments}{ 156 | \if{html}{\out{
}} 157 | \describe{ 158 | \item{\code{deep}}{Whether to make a deep clone.} 159 | } 160 | \if{html}{\out{
}} 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /R/SessionState.R: -------------------------------------------------------------------------------- 1 | #' @title 2 | #' SessionState 3 | #' 4 | #' @description 5 | #' This class holds the state of a background [`session`][`callr::r_session`] 6 | #' used by an asynchronous backend (i.e., [`parabar::AsyncBackend`]). See the 7 | #' **Details** section for more information. 8 | #' 9 | #' @details 10 | #' The session state is useful to check if an asynchronous backend is ready for 11 | #' certain operations. A session can only be in one of the following four states 12 | #' at a time: 13 | #' - `session_is_starting`: When `TRUE`, it indicates that the session is 14 | #' starting. 15 | #' - `session_is_idle`: When `TRUE`, it indicates that the session is idle and 16 | #' ready to execute operations. 17 | #' - `session_is_busy`: When `TRUE`, it indicates that the session is busy 18 | #' (i.e., see the [`parabar::TaskState`] class for more information about a 19 | #' task's state). 20 | #' - `session_is_finished`: When `TRUE`, it indicates that the session is closed 21 | #' and no longer available for operations. 22 | #' 23 | #' @examples 24 | #' # Handy function to print the session states all at once. 25 | #' check_state <- function(session) { 26 | #' # Create a session object and determine its state. 27 | #' session_state <- SessionState$new(session) 28 | #' 29 | #' # Print the state. 30 | #' cat( 31 | #' "Session is starting: ", session_state$session_is_starting, "\n", 32 | #' "Session is idle: ", session_state$session_is_idle, "\n", 33 | #' "Session is busy: ", session_state$session_is_busy, "\n", 34 | #' "Session is finished: ", session_state$session_is_finished, "\n", 35 | #' sep = "" 36 | #' ) 37 | #' } 38 | #' 39 | #' # Create a specification object. 40 | #' specification <- Specification$new() 41 | #' 42 | #' # Set the number of cores. 43 | #' specification$set_cores(cores = 2) 44 | #' 45 | #' # Set the cluster type. 46 | #' specification$set_type(type = "psock") 47 | #' 48 | #' # Create an asynchronous backend object. 49 | #' backend <- AsyncBackend$new() 50 | #' 51 | #' # Start the cluster on the backend. 52 | #' backend$start(specification) 53 | #' 54 | #' # Check that the session is idle. 55 | #' check_state(backend$cluster) 56 | #' 57 | #' { 58 | #' # Run a task in parallel (i.e., approx. 0.25 seconds). 59 | #' backend$sapply( 60 | #' x = 1:10, 61 | #' fun = function(x) { 62 | #' # Sleep a bit. 63 | #' Sys.sleep(0.05) 64 | #' 65 | #' # Compute something. 66 | #' output <- x + 1 67 | #' 68 | #' # Return the result. 69 | #' return(output) 70 | #' } 71 | #' ) 72 | #' 73 | #' # And immediately check that the session is busy. 74 | #' check_state(backend$cluster) 75 | #' } 76 | #' 77 | #' # Get the output and wait for the task to complete. 78 | #' output <- backend$get_output(wait = TRUE) 79 | #' 80 | #' # Check that the session is idle again. 81 | #' check_state(backend$cluster) 82 | #' 83 | #' # Manually close the session. 84 | #' backend$cluster$close() 85 | #' 86 | #' # Check that the session is finished. 87 | #' check_state(backend$cluster) 88 | #' 89 | #' # Stop the backend. 90 | #' backend$stop() 91 | #' 92 | #' @seealso 93 | #' [`parabar::TaskState`], [`parabar::AsyncBackend`] and 94 | #' [`parabar::ProgressTrackingContext`]. 95 | #' 96 | #' @export 97 | SessionState <- R6::R6Class("SessionState", 98 | private = list( 99 | # Session state fields. 100 | .session_is_starting = NULL, 101 | .session_is_idle = NULL, 102 | .session_is_busy = NULL, 103 | .session_is_finished = NULL, 104 | 105 | # Set the session state. 106 | .set_state = function(session) { 107 | # Get the session state. 108 | state <- session$get_state() 109 | 110 | # Set the session state. 111 | private$.session_is_starting <- state == "starting" 112 | private$.session_is_idle <- state == "idle" 113 | private$.session_is_busy <- state == "busy" 114 | private$.session_is_finished <- state == "finished" 115 | } 116 | ), 117 | 118 | public = list( 119 | #' @description 120 | #' Create a new [`parabar::SessionState`] object and determine the state 121 | #' of a given background [`session`][`callr::r_session`]. 122 | #' 123 | #' @param session A [`callr::r_session`] object. 124 | #' 125 | #' @return 126 | #' An object of class [`parabar::SessionState`]. 127 | initialize = function(session) { 128 | # Set the session state. 129 | private$.set_state(session) 130 | } 131 | ), 132 | 133 | active = list( 134 | #' @field session_is_starting A logical value indicating whether the 135 | #' session is starting. 136 | session_is_starting = function() { 137 | return(private$.session_is_starting) 138 | }, 139 | 140 | #' @field session_is_idle A logical value indicating whether the session 141 | #' is idle and ready to execute operations. 142 | session_is_idle = function() { 143 | return(private$.session_is_idle) 144 | }, 145 | 146 | #' @field session_is_busy A logical value indicating whether the session 147 | #' is busy. 148 | session_is_busy = function() { 149 | return(private$.session_is_busy) 150 | }, 151 | 152 | #' @field session_is_finished A logical value indicating whether the 153 | #' session is closed and no longer available for operations. 154 | session_is_finished = function() { 155 | return(private$.session_is_finished) 156 | } 157 | ) 158 | ) 159 | -------------------------------------------------------------------------------- /R/exports.R: -------------------------------------------------------------------------------- 1 | #' @include Options.R Helper.R Specification.R BackendFactory.R ContextFactory.R Exception.R BarFactory.R 2 | 3 | #' @template set-default-options 4 | #' @order 3 5 | #' @export 6 | set_default_options <- function() { 7 | # Set `Options` instance. 8 | options(parabar = Options$new()) 9 | 10 | # Remain silent. 11 | invisible(NULL) 12 | } 13 | 14 | 15 | #' @template get-option 16 | #' @order 1 17 | #' @export 18 | get_option <- function(option) { 19 | # Invoke the helper. 20 | Helper$get_option(option) 21 | } 22 | 23 | 24 | #' @template set-option 25 | #' @order 2 26 | #' @export 27 | set_option <- function(option, value) { 28 | # Invoke the helper. 29 | Helper$set_option(option, value) 30 | 31 | # Remain silent. 32 | invisible() 33 | } 34 | 35 | 36 | #' @template configure-bar 37 | #' @export 38 | configure_bar <- function(type = "modern", ...) { 39 | # If the type is not known. 40 | if (!type %in% c("modern", "basic")) { 41 | # Throw an error. 42 | Exception$feature_not_developed() 43 | } 44 | 45 | # Update the bar type in options. 46 | set_option("progress_bar_type", type) 47 | 48 | # Capture the bar configuration requested by the user. 49 | user_bar_config <- list(...) 50 | 51 | # If the configuration is not empty. 52 | if (length(user_bar_config)) { 53 | # Get the default config options. 54 | bar_config <- get_option("progress_bar_config") 55 | 56 | # Combine the configurations. 57 | bar_config[[type]] <- utils::modifyList(bar_config[[type]], user_bar_config) 58 | 59 | # Set the bar config in options. 60 | set_option("progress_bar_config", bar_config) 61 | } 62 | 63 | # Remain silent. 64 | invisible() 65 | } 66 | 67 | 68 | #' @template start-backend 69 | #' @export 70 | start_backend <- function(cores, cluster_type = "psock", backend_type = "async") { 71 | # Create specification object. 72 | specification <- Specification$new() 73 | 74 | # Set specification cores. 75 | specification$set_cores(cores) 76 | 77 | # Set the specification cluster type. 78 | specification$set_type(cluster_type) 79 | 80 | # Initialize a backend factory. 81 | backend_factory <- BackendFactory$new() 82 | 83 | # Get a backend instance of the desired type. 84 | backend <- backend_factory$get(backend_type) 85 | 86 | # Start the backend. 87 | backend$start(specification) 88 | 89 | # Register a finalizer. 90 | reg.finalizer(backend, function(backend) { 91 | # If the backend is still active when the finalizer runs in the future. 92 | if (backend$active) { 93 | # Attempt to stop the backend in their stead. 94 | tryCatch( 95 | # The expression to evaluate. 96 | expr = { 97 | # Stop it. 98 | stop_backend(backend) 99 | }, 100 | # Catch any errors. 101 | error = function(error) { 102 | # Issue a warning. 103 | Warning$error_in_backend_finalizer(backend, error) 104 | } 105 | ) 106 | } 107 | }, onexit = TRUE) 108 | 109 | # Return the backend. 110 | return(backend) 111 | } 112 | 113 | 114 | #' @template stop-backend 115 | #' @export 116 | stop_backend <- function(backend) { 117 | # Check the type. 118 | Helper$check_object_type(backend, "Backend") 119 | 120 | # Stop the backend 121 | backend$stop() 122 | 123 | # Remain silent. 124 | invisible() 125 | } 126 | 127 | 128 | #' @template clear 129 | #' @export 130 | clear <- function(backend) { 131 | # Check the type. 132 | Helper$check_object_type(backend, "Backend") 133 | 134 | # Peek the backend. 135 | backend$clear() 136 | } 137 | 138 | 139 | #' @template peek 140 | #' @export 141 | peek <- function(backend) { 142 | # Check the type. 143 | Helper$check_object_type(backend, "Backend") 144 | 145 | # Peek the backend.. 146 | backend$peek() 147 | } 148 | 149 | 150 | #' @template export 151 | #' @export 152 | export <- function(backend, variables, environment) { 153 | # Check the type. 154 | Helper$check_object_type(backend, "Backend") 155 | 156 | # Export variables. 157 | backend$export(variables, environment) 158 | } 159 | 160 | 161 | #' @template evaluate 162 | #' @export 163 | evaluate <- function(backend, expression) { 164 | # Check the type. 165 | Helper$check_object_type(backend, "Backend") 166 | 167 | # Capture the expression. 168 | capture <- substitute(expression) 169 | 170 | # Prepare the call. 171 | capture_call <- bquote(backend$evaluate(.(capture))) 172 | 173 | # Perform the call. 174 | eval(capture_call) 175 | } 176 | 177 | 178 | #' @template par-sapply 179 | #' @export 180 | par_sapply <- function(backend = NULL, x, fun, ...) { 181 | # Create an user API consumer. 182 | consumer <- UserApiConsumer$new() 183 | 184 | # Execute the task using the `sapply` parallel operation. 185 | consumer$sapply(backend = backend, x = x, fun = fun, ...) 186 | } 187 | 188 | #' @template par-lapply 189 | #' @export 190 | par_lapply <- function(backend = NULL, x, fun, ...) { 191 | # Create an user API consumer. 192 | consumer <- UserApiConsumer$new() 193 | 194 | # Execute the task using the `lapply` parallel operation. 195 | consumer$lapply(backend = backend, x = x, fun = fun, ...) 196 | } 197 | 198 | #' @template par-apply 199 | #' @export 200 | par_apply <- function(backend = NULL, x, margin, fun, ...) { 201 | # Create an user API consumer. 202 | consumer <- UserApiConsumer$new() 203 | 204 | # Execute the task using the `apply` parallel operation. 205 | consumer$apply(backend = backend, x = x, margin = margin, fun = fun, ...) 206 | } 207 | -------------------------------------------------------------------------------- /man/TaskState.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/TaskState.R 3 | \name{TaskState} 4 | \alias{TaskState} 5 | \title{TaskState} 6 | \description{ 7 | This class holds the state of a task deployed to an asynchronous backend 8 | (i.e., \code{\link{AsyncBackend}}). See the \strong{Details} section for more 9 | information. 10 | } 11 | \details{ 12 | The task state is useful to check if an asynchronous backend is free to 13 | execute other operations. A task can only be in one of the following three 14 | states at a time: 15 | \itemize{ 16 | \item \code{task_not_started}: When \code{TRUE}, it indicates whether the backend is free 17 | to execute another operation. 18 | \item \code{task_is_running}: When \code{TRUE}, it indicates that there is a task running 19 | on the backend. 20 | \item \code{task_is_completed}: When \code{TRUE}, it indicates that the task has been 21 | completed, but the backend is still busy because the task output has not 22 | been retrieved. 23 | } 24 | 25 | The task state is determined based on the state of the background 26 | \code{\link[callr:r_session]{session}} (i.e., see the \code{get_state} method for 27 | \code{\link[callr:r_session]{callr::r_session}}) and the state of the task execution inferred from 28 | polling the process (i.e., see the \code{poll_process} method for 29 | \code{\link[callr:r_session]{callr::r_session}}) as follows:\tabular{ccccc}{ 30 | Session State \tab Execution State \tab Not Started \tab Is Running \tab Is Completed \cr 31 | \code{idle} \tab \code{timeout} \tab \code{TRUE} \tab \code{FALSE} \tab \code{FALSE} \cr 32 | \code{busy} \tab \code{timeout} \tab \code{FALSE} \tab \code{TRUE} \tab \code{FALSE} \cr 33 | \code{busy} \tab \code{ready} \tab \code{FALSE} \tab \code{FALSE} \tab \code{TRUE} \cr 34 | } 35 | } 36 | \examples{ 37 | # Handy function to print the task states all at once. 38 | check_state <- function(session) { 39 | # Create a task state object and determine the state. 40 | task_state <- TaskState$new(session) 41 | 42 | # Print the state. 43 | cat( 44 | "Task not started: ", task_state$task_not_started, "\n", 45 | "Task is running: ", task_state$task_is_running, "\n", 46 | "Task is completed: ", task_state$task_is_completed, "\n", 47 | sep = "" 48 | ) 49 | } 50 | 51 | # Create a specification object. 52 | specification <- Specification$new() 53 | 54 | # Set the number of cores. 55 | specification$set_cores(cores = 2) 56 | 57 | # Set the cluster type. 58 | specification$set_type(type = "psock") 59 | 60 | # Create an asynchronous backend object. 61 | backend <- AsyncBackend$new() 62 | 63 | # Start the cluster on the backend. 64 | backend$start(specification) 65 | 66 | # Check that the task has not been started (i.e., the backend is free). 67 | check_state(backend$cluster) 68 | 69 | { 70 | # Run a task in parallel (i.e., approx. 0.25 seconds). 71 | backend$sapply( 72 | x = 1:10, 73 | fun = function(x) { 74 | # Sleep a bit. 75 | Sys.sleep(0.05) 76 | 77 | # Compute something. 78 | output <- x + 1 79 | 80 | # Return the result. 81 | return(output) 82 | } 83 | ) 84 | 85 | # And immediately check the state to see that the task is running. 86 | check_state(backend$cluster) 87 | } 88 | 89 | # Sleep for a bit to wait for the task to complete. 90 | Sys.sleep(1) 91 | 92 | # Check that the task is completed (i.e., the output needs to be retrieved). 93 | check_state(backend$cluster) 94 | 95 | # Get the output. 96 | output <- backend$get_output(wait = TRUE) 97 | 98 | # Check that the task has not been started (i.e., the backend is free again). 99 | check_state(backend$cluster) 100 | 101 | # Stop the backend. 102 | backend$stop() 103 | 104 | } 105 | \seealso{ 106 | \code{\link{SessionState}}, \code{\link{AsyncBackend}} and 107 | \code{\link{ProgressTrackingContext}}. 108 | } 109 | \section{Active bindings}{ 110 | \if{html}{\out{
}} 111 | \describe{ 112 | \item{\code{task_not_started}}{A logical value indicating whether the task 113 | has been started. It is used to determine if the backend is free to 114 | execute another operation.} 115 | 116 | \item{\code{task_is_running}}{A logical value indicating whether the task is 117 | running.} 118 | 119 | \item{\code{task_is_completed}}{A logical value indicating whether the task 120 | has been completed and the output needs to be retrieved.} 121 | } 122 | \if{html}{\out{
}} 123 | } 124 | \section{Methods}{ 125 | \subsection{Public methods}{ 126 | \itemize{ 127 | \item \href{#method-TaskState-new}{\code{TaskState$new()}} 128 | \item \href{#method-TaskState-clone}{\code{TaskState$clone()}} 129 | } 130 | } 131 | \if{html}{\out{
}} 132 | \if{html}{\out{}} 133 | \if{latex}{\out{\hypertarget{method-TaskState-new}{}}} 134 | \subsection{Method \code{new()}}{ 135 | Create a new \code{\link{TaskState}} object and determine the state of 136 | a task on a given background \code{\link[callr:r_session]{session}}. 137 | \subsection{Usage}{ 138 | \if{html}{\out{
}}\preformatted{TaskState$new(session)}\if{html}{\out{
}} 139 | } 140 | 141 | \subsection{Arguments}{ 142 | \if{html}{\out{
}} 143 | \describe{ 144 | \item{\code{session}}{A \code{\link[callr:r_session]{callr::r_session}} object.} 145 | } 146 | \if{html}{\out{
}} 147 | } 148 | \subsection{Returns}{ 149 | An object of class \code{\link{TaskState}}. 150 | } 151 | } 152 | \if{html}{\out{
}} 153 | \if{html}{\out{}} 154 | \if{latex}{\out{\hypertarget{method-TaskState-clone}{}}} 155 | \subsection{Method \code{clone()}}{ 156 | The objects of this class are cloneable with this method. 157 | \subsection{Usage}{ 158 | \if{html}{\out{
}}\preformatted{TaskState$clone(deep = FALSE)}\if{html}{\out{
}} 159 | } 160 | 161 | \subsection{Arguments}{ 162 | \if{html}{\out{
}} 163 | \describe{ 164 | \item{\code{deep}}{Whether to make a deep clone.} 165 | } 166 | \if{html}{\out{
}} 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /tests/testthat/test-specification.R: -------------------------------------------------------------------------------- 1 | # Test `Specification` class. 2 | 3 | test_that("'Specification' fails on single core machines", { 4 | # Create a specification object. 5 | specification <- SpecificationTester$new() 6 | 7 | # Pretend the machine has a single core. 8 | specification$available_cores <- 1 9 | 10 | # Expect failure regardless of the number of requested cores. 11 | expect_error(specification$set_cores(cores = 1), as_text(Exception$not_enough_cores())) 12 | expect_error(specification$set_cores(cores = 2), as_text(Exception$not_enough_cores())) 13 | expect_error(specification$set_cores(cores = 7), as_text(Exception$not_enough_cores())) 14 | }) 15 | 16 | 17 | test_that("'Specification' sets the number of cores correctly", { 18 | # Create a specification object. 19 | specification <- SpecificationTester$new() 20 | 21 | # Suppose the machine has two cores. 22 | specification$available_cores <- 2 23 | 24 | # When zero cores are requested. 25 | expect_warning(specification$set_cores(cores = 0), as_text(Warning$requested_cluster_cores_too_low())) 26 | expect_equal(specification$cores, 1) 27 | 28 | # When one core is requested. 29 | specification$set_cores(cores = 1) 30 | expect_equal(specification$cores, 1) 31 | 32 | # When two cores are requested. 33 | expect_warning(specification$set_cores(cores = 2), as_text(Warning$requested_cluster_cores_too_high(specification$usable_cores))) 34 | expect_equal(specification$cores, 1) 35 | 36 | # When more than two cores are requested. 37 | expect_warning(specification$set_cores(cores = 7), as_text(Warning$requested_cluster_cores_too_high(specification$usable_cores))) 38 | expect_equal(specification$cores, 1) 39 | 40 | # Suppose the machine has three cores. 41 | specification$available_cores <- 3 42 | 43 | # When zero cores are requested. 44 | expect_warning(specification$set_cores(cores = 0), as_text(Warning$requested_cluster_cores_too_low())) 45 | expect_equal(specification$cores, 1) 46 | 47 | # When one core is requested. 48 | specification$set_cores(cores = 1) 49 | expect_equal(specification$cores, 1) 50 | 51 | # When two cores are requested. 52 | specification$set_cores(cores = 2) 53 | expect_equal(specification$cores, 2) 54 | 55 | # When three cores are requested. 56 | expect_warning(specification$set_cores(cores = 3), as_text(Warning$requested_cluster_cores_too_high(specification$usable_cores))) 57 | expect_equal(specification$cores, 2) 58 | 59 | # Suppose the machine has eight cores. 60 | specification$available_cores <- 8 61 | 62 | # When zero cores are requested. 63 | expect_warning(specification$set_cores(cores = 0), as_text(Warning$requested_cluster_cores_too_low())) 64 | expect_equal(specification$cores, 1) 65 | 66 | # When one core is requested. 67 | specification$set_cores(cores = 1) 68 | expect_equal(specification$cores, 1) 69 | 70 | # When two cores are requested. 71 | specification$set_cores(cores = 2) 72 | expect_equal(specification$cores, 2) 73 | 74 | # When seven cores are requested. 75 | specification$set_cores(cores = 7) 76 | expect_equal(specification$cores, 7) 77 | 78 | # When eight cores are requested. 79 | expect_warning(specification$set_cores(cores = 8), as_text(Warning$requested_cluster_cores_too_high(specification$usable_cores))) 80 | expect_equal(specification$cores, 7) 81 | 82 | # When nine cores are requested. 83 | expect_warning(specification$set_cores(cores = 9), as_text(Warning$requested_cluster_cores_too_high(specification$usable_cores))) 84 | expect_equal(specification$cores, 7) 85 | }) 86 | 87 | 88 | test_that("'Specification' sets the cluster type on Unix correctly", { 89 | # Skip test on Windows. 90 | skip_on_os("windows") 91 | 92 | # Create specification object. 93 | specification <- Specification$new() 94 | 95 | # Let the specification determine the cluster type. 96 | specification$set_type(type = NULL) 97 | 98 | # Expect a `FORK` cluster was determined as default on Unix platforms. 99 | expect_equal(specification$type, c(unix = "FORK")) 100 | 101 | # Specify the `FORK` type explicitly. 102 | specification$set_type(type = "fork") 103 | 104 | # Expect the correct type was set. 105 | expect_equal(specification$type, "FORK") 106 | 107 | # Specify the `PSOCK` type explicitly. 108 | specification$set_type(type = "psock") 109 | 110 | # Expect the correct type was set. 111 | expect_equal(specification$type, "PSOCK") 112 | 113 | # Expect warning when specifying an invalid type. 114 | expect_warning( 115 | specification$set_type(type = "invalid"), 116 | as_text(Warning$requested_cluster_type_not_supported(specification$types)) 117 | ) 118 | }) 119 | 120 | 121 | test_that("'Specification' sets the cluster type on Windows correctly", { 122 | # Skip test on Unix and the like. 123 | skip_on_os(c("mac", "linux", "solaris")) 124 | 125 | # Create specification object. 126 | specification <- Specification$new() 127 | 128 | # Let the specification determine the cluster type. 129 | specification$set_type(type = NULL) 130 | 131 | # Expect a `PSOCK` cluster was determined as default on Windows platforms. 132 | expect_equal(specification$type, c(windows = "PSOCK")) 133 | 134 | # Expect warning if a `FORK` cluster is requested on Windows platforms. 135 | expect_warning( 136 | specification$set_type(type = "fork"), 137 | as_text(Warning$requested_cluster_type_not_compatible(specification$types)) 138 | ) 139 | 140 | # Expect that the correct type was set regardless the incompatible request. 141 | expect_equal(specification$type, c(windows = "PSOCK")) 142 | 143 | # Specify the `PSOCK` type explicitly. 144 | specification$set_type(type = "psock") 145 | 146 | # Expect the correct type was set. 147 | expect_equal(specification$type, "PSOCK") 148 | 149 | # Expect warning when specifying an invalid type. 150 | expect_warning( 151 | specification$set_type(type = "invalid"), 152 | as_text(Warning$requested_cluster_type_not_supported(specification$types)) 153 | ) 154 | }) 155 | -------------------------------------------------------------------------------- /R/TaskState.R: -------------------------------------------------------------------------------- 1 | #' @title 2 | #' TaskState 3 | #' 4 | #' @description 5 | #' This class holds the state of a task deployed to an asynchronous backend 6 | #' (i.e., [`parabar::AsyncBackend`]). See the **Details** section for more 7 | #' information. 8 | #' 9 | #' @details 10 | #' The task state is useful to check if an asynchronous backend is free to 11 | #' execute other operations. A task can only be in one of the following three 12 | #' states at a time: 13 | #' - `task_not_started`: When `TRUE`, it indicates whether the backend is free 14 | #' to execute another operation. 15 | #' - `task_is_running`: When `TRUE`, it indicates that there is a task running 16 | #' on the backend. 17 | #' - `task_is_completed`: When `TRUE`, it indicates that the task has been 18 | #' completed, but the backend is still busy because the task output has not 19 | #' been retrieved. 20 | #' 21 | #' The task state is determined based on the state of the background 22 | #' [`session`][`callr::r_session`] (i.e., see the `get_state` method for 23 | #' [`callr::r_session`]) and the state of the task execution inferred from 24 | #' polling the process (i.e., see the `poll_process` method for 25 | #' [`callr::r_session`]) as follows: 26 | #' 27 | #' | Session State | Execution State | Not Started | Is Running | Is Completed | 28 | #' | :-----------: | :-------------: | :---------: | :--------: | :----------: | 29 | #' | `idle` | `timeout` | `TRUE` | `FALSE` | `FALSE` | 30 | #' | `busy` | `timeout` | `FALSE` | `TRUE` | `FALSE` | 31 | #' | `busy` | `ready` | `FALSE` | `FALSE` | `TRUE` | 32 | #' 33 | #' @examples 34 | #' # Handy function to print the task states all at once. 35 | #' check_state <- function(session) { 36 | #' # Create a task state object and determine the state. 37 | #' task_state <- TaskState$new(session) 38 | #' 39 | #' # Print the state. 40 | #' cat( 41 | #' "Task not started: ", task_state$task_not_started, "\n", 42 | #' "Task is running: ", task_state$task_is_running, "\n", 43 | #' "Task is completed: ", task_state$task_is_completed, "\n", 44 | #' sep = "" 45 | #' ) 46 | #' } 47 | #' 48 | #' # Create a specification object. 49 | #' specification <- Specification$new() 50 | #' 51 | #' # Set the number of cores. 52 | #' specification$set_cores(cores = 2) 53 | #' 54 | #' # Set the cluster type. 55 | #' specification$set_type(type = "psock") 56 | #' 57 | #' # Create an asynchronous backend object. 58 | #' backend <- AsyncBackend$new() 59 | #' 60 | #' # Start the cluster on the backend. 61 | #' backend$start(specification) 62 | #' 63 | #' # Check that the task has not been started (i.e., the backend is free). 64 | #' check_state(backend$cluster) 65 | #' 66 | #' { 67 | #' # Run a task in parallel (i.e., approx. 0.25 seconds). 68 | #' backend$sapply( 69 | #' x = 1:10, 70 | #' fun = function(x) { 71 | #' # Sleep a bit. 72 | #' Sys.sleep(0.05) 73 | #' 74 | #' # Compute something. 75 | #' output <- x + 1 76 | #' 77 | #' # Return the result. 78 | #' return(output) 79 | #' } 80 | #' ) 81 | #' 82 | #' # And immediately check the state to see that the task is running. 83 | #' check_state(backend$cluster) 84 | #' } 85 | #' 86 | #' # Sleep for a bit to wait for the task to complete. 87 | #' Sys.sleep(1) 88 | #' 89 | #' # Check that the task is completed (i.e., the output needs to be retrieved). 90 | #' check_state(backend$cluster) 91 | #' 92 | #' # Get the output. 93 | #' output <- backend$get_output(wait = TRUE) 94 | #' 95 | #' # Check that the task has not been started (i.e., the backend is free again). 96 | #' check_state(backend$cluster) 97 | #' 98 | #' # Stop the backend. 99 | #' backend$stop() 100 | #' 101 | #' @seealso 102 | #' [`parabar::SessionState`], [`parabar::AsyncBackend`] and 103 | #' [`parabar::ProgressTrackingContext`]. 104 | #' 105 | #' @export 106 | TaskState <- R6::R6Class("TaskState", 107 | private = list( 108 | # Task state fields. 109 | .task_not_started = NULL, 110 | .task_is_running = NULL, 111 | .task_is_completed = NULL, 112 | 113 | # Set the task state. 114 | .set_state = function(cluster) { 115 | # Session state. 116 | session_state <- cluster$get_state() 117 | 118 | # Task execution state. 119 | execution_state <- cluster$poll_process(0) 120 | 121 | # Compute all states. 122 | # The session is free and no task is running. 123 | private$.task_not_started <- (session_state == "idle" && execution_state == "timeout") 124 | 125 | # The session is busy and a task is running. 126 | private$.task_is_running <- (session_state == "busy" && execution_state == "timeout") 127 | 128 | # The session is busy and a task has completed (i.e., results need to be read). 129 | private$.task_is_completed <- (session_state == "busy" && execution_state == "ready") 130 | } 131 | ), 132 | 133 | public = list( 134 | #' @description 135 | #' Create a new [`parabar::TaskState`] object and determine the state of 136 | #' a task on a given background [`session`][`callr::r_session`]. 137 | #' 138 | #' @param session A [`callr::r_session`] object. 139 | #' 140 | #' @return 141 | #' An object of class [`parabar::TaskState`]. 142 | initialize = function(session) { 143 | # Set the task state. 144 | private$.set_state(session) 145 | } 146 | ), 147 | 148 | active = list( 149 | #' @field task_not_started A logical value indicating whether the task 150 | #' has been started. It is used to determine if the backend is free to 151 | #' execute another operation. 152 | task_not_started = function() { 153 | return(private$.task_not_started) 154 | }, 155 | 156 | #' @field task_is_running A logical value indicating whether the task is 157 | #' running. 158 | task_is_running = function() { 159 | return(private$.task_is_running) 160 | }, 161 | 162 | #' @field task_is_completed A logical value indicating whether the task 163 | #' has been completed and the output needs to be retrieved. 164 | task_is_completed = function() { 165 | return(private$.task_is_completed) 166 | } 167 | ) 168 | ) 169 | --------------------------------------------------------------------------------