Remoc ๐Ÿฆ‘ โ€” Remote multiplexed objects and channels for Rust

Related tags

Utilities remoc
Overview

Remoc ๐Ÿฆ‘ โ€” remote multiplexed objects and channels

Remoc makes remote interaction between Rust programs seamless and smooth. Over a single underlying transport, it provides:

  • multiple channels of different types like MPSC, oneshot, watch, etc.,
  • remote synchronization primitives,
  • calling of remote functions and trait methods on a remote object (RPC).

Remoc is written in 100% safe Rust, builds upon Tokio, works with any type and data format supported by Serde and does not depend on any particular transport type.

crates.io page docs.rs page Apache 2 license codecov Discord chat

Introduction

A common pattern in Rust programs is to use channels to communicate between threads and async tasks. Setting up a channel is done in a single line and it largely avoids the need for shared state and the associated complexity. Remoc extends this programming model to distributed systems by providing channels that work seamlessly over remote connections.

For that it uses Serde to serialize and deserialize data as it gets transmitted over an underlying transport, which might be a TCP network connection, a WebSocket, UNIX pipe, or even a serial link.

Opening a new channel is straightforward, just send the sender or receiver half of the new channel over an existing channel, like you would do between local threads and tasks. All channels are multiplexed over the same remote connection, with data being transmitted in chunks to avoid one channel blocking another if a large message is transmitted.

Building upon its remote channels, Remoc allows calling of remote functions and closures. Furthermore, a trait can be made remotely callable with automatically generated client and server implementations, resembling a classical remote procedure calling (RPC) model.

Forward and backward compatibility

Distributed systems often require that endpoints running different software versions interact. By utilizing a self-describing data format like JSON for encoding of your data for transport, you can ensure a high level of backward and forward compatibility.

It is always possible to add new fields to enums and struct and utilize the #[serde(default)] attribute to provide default values for that field if it was sent by an older client that has no knowledge of it. Likewise additional, unknown fields are silently ignored when received, allowing you to extend the data format without disturbing legacy endpoints.

Check the documentation of the employed data format for details.

Crate features

Most functionality of Remoc is gated by crate features. The following features are available:

  • serde enables the codec module and implements serialize and deserialize for all configuration and error types.
  • rch enables remote channels provided by the rch module.
  • rfn enables remote function calling provided by the rfn module.
  • robj enables remote object utilities provided by the robj module.
  • rtc enables remote trait calling provided by the rtc module.

The meta-feature full enables all features from above but no codecs.

The following features enable data formats for transmission:

  • codec-bincode provides the Bincode format.
  • codec-cbor provides the CBOR format.
  • codec-json provides the JSON format.
  • codec-message-pack provides the MessagePack format.

The feature default-codec-* selects the respective codec as default. At most one of this must be selected and this should only be used by applications, not libraries.

By default all features are enabled and the JSON codec is used as default.

Supported Rust versions

Remoc is built against the latest stable release. The minimum supported Rust version (MSRV) is 1.51.

Example

In the following example the server listens on TCP port 9870 and the client connects to it. Then both ends establish a Remoc connection using Connect::io() over the TCP connection. The connection dispatchers are spawned onto new tasks and the client() and server() functions are called with the established base channel. Then, the client creates a new remote MPSC channel and sends it inside a count request to the server. The server receives the count request and counts on the provided channel. The client receives each counted number over the new channel.

use std::net::Ipv4Addr;
use tokio::net::{TcpStream, TcpListener};
use remoc::prelude::*;

#[tokio::main]
async fn main() {
    // For demonstration we run both client and server in
    // the same process. In real life connect_client() and
    // connect_server() would run on different machines.
    futures::join!(connect_client(), connect_server());
}

// This would be run on the client.
// It establishes a Remoc connection over TCP to the server.
async fn connect_client() {
    // Wait for server to be ready.
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;

    // Establish TCP connection.
    let socket =
        TcpStream::connect((Ipv4Addr::LOCALHOST, 9870)).await.unwrap();
    let (socket_rx, socket_tx) = socket.into_split();

    // Establish Remoc connection over TCP.
    // The connection is always bidirectional, but we can just drop
    // the unneeded receiver.
    let (conn, tx, _rx): (_, _, rch::base::Receiver<()>) =
        remoc::Connect::io(remoc::Cfg::default(), socket_rx, socket_tx)
        .await.unwrap();
    tokio::spawn(conn);

    // Run client.
    client(tx).await;
}

// This would be run on the server.
// It accepts a Remoc connection over TCP from the client.
async fn connect_server() {
    // Listen for incoming TCP connection.
    let listener =
        TcpListener::bind((Ipv4Addr::LOCALHOST, 9870)).await.unwrap();
    let (socket, _) = listener.accept().await.unwrap();
    let (socket_rx, socket_tx) = socket.into_split();

    // Establish Remoc connection over TCP.
    // The connection is always bidirectional, but we can just drop
    // the unneeded sender.
    let (conn, _tx, rx): (_, rch::base::Sender<()>, _) =
        remoc::Connect::io(remoc::Cfg::default(), socket_rx, socket_tx)
        .await.unwrap();
    tokio::spawn(conn);

    // Run server.
    server(rx).await;
}

// User-defined data structures needs to implement Serialize
// and Deserialize.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct CountReq {
    up_to: u32,
    // Most Remoc types like channels can be included in serializable
    // data structures for transmission to remote endpoints.
    seq_tx: rch::mpsc::Sender<u32>,
}

// This would be run on the client.
// It sends a count request to the server and receives each number
// as it is counted over a newly established MPSC channel.
async fn client(mut tx: rch::base::Sender<CountReq>) {
    // By sending seq_tx over an existing remote channel, a new remote
    // channel is automatically created and connected to the server.
    // This all happens inside the existing TCP connection.
    let (seq_tx, mut seq_rx) = rch::mpsc::channel(1);
    tx.send(CountReq { up_to: 4, seq_tx }).await.unwrap();

    // Receive counted numbers over new channel.
    assert_eq!(seq_rx.recv().await.unwrap(), Some(0));
    assert_eq!(seq_rx.recv().await.unwrap(), Some(1));
    assert_eq!(seq_rx.recv().await.unwrap(), Some(2));
    assert_eq!(seq_rx.recv().await.unwrap(), Some(3));
    assert_eq!(seq_rx.recv().await.unwrap(), None);
}

// This would be run on the server.
// It receives a count request from the client and sends each number
// as it is counted over the MPSC channel sender provided by the client.
async fn server(mut rx: rch::base::Receiver<CountReq>) {
    // Receive count request and channel sender to use for counting.
    while let Some(CountReq {up_to, seq_tx}) = rx.recv().await.unwrap()
    {
        for i in 0..up_to {
            // Send each counted number over provided channel.
            seq_tx.send(i).await.unwrap();
        }
    }
}

Sponsors

Development of Remoc is partially sponsored by ENQT GmbH.

ENQT Logo

License

Remoc is licensed under the Apache 2.0 license.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Remoc by you, shall be licensed as Apache 2.0, without any additional terms or conditions.

Comments
  • Compile error with lifetimes on remote traits

    Compile error with lifetimes on remote traits

    The rtc::remote attribute make a compilation error when used on a trait with lifetimes. This happen for example when using a Cow as return type to avoid unnecessary allocations.

    Example

    #[rtc::remote]
    pub trait ExampleBorrowed {
        async fn hello(&self) -> Result<Cow<'_, String>, CallError>;
    }
    
    Compiler error
    error[E0106]: missing lifetime specifier
      --> transport/src/cache.rs:45:41
       |
    45 |     async fn hello(&self) -> Result<Cow<'_, String>, CallError>;
       |                                         ^^ expected named lifetime parameter
       |
    help: consider introducing a named lifetime parameter
       |
    43 ~ 'a, #[rtc::remote]
    44 | pub trait ExampleBorrowed {
    45 ~     async fn hello(&self) -> Result<Cow<'a, String>, CallError>;
       |
    

    The compiler expect a named lifetime, while it can be elided in this case. When a named lifetime is provided, another compile errors occurs.

    Compiler error with named lifetime

    With function lifetime:

    #[rtc::remote]
    pub trait ExampleBorrowed {
        async fn hello<'a>(&self) -> Result<Cow<'a, String>, CallError>;
    }
    
    error: expected parentheses
      --> transport/src/cache.rs:44:19
       |
    44 |     async fn hello<'a>(&self) -> Result<Cow<'a, String>, CallError>;
       |    
    

    With trait lifetime:

    #[rtc::remote]
    pub trait ExampleBorrowed<'a> {
        async fn hello(&self) -> Result<Cow<'a, String>, CallError>;
    }
    
    error: lifetime in trait object type must be followed by `+`
      --> transport/src/cache.rs:43:27
       |
    43 | pub trait ExampleBorrowed<'a> {
       |                           ^^
    
    error: expected at least one type
      --> transport/src/cache.rs:42:1
       |
    42 | #[rtc::remote]
       | ^^^^^^^^^^^^^^
       |
       = note: this error originates in the attribute macro `rtc::remote` (in Nightly builds, run with -Z macro-backtrace for more info)
    
    error[E0782]: trait objects must include the `dyn` keyword
      --> transport/src/cache.rs:43:27
       |
    43 | pub trait ExampleBorrowed<'a> {
       |                           ^^
       |
    help: add `dyn` keyword before this trait
       |
    43 | pub trait ExampleBorrowed<dyn 'a> {
       |                           +++
    
    error[E0224]: at least one trait is required for an object type
      --> transport/src/cache.rs:43:27
       |
    43 | pub trait ExampleBorrowed<'a> {
       |   
    
    opened by baptiste0928 6
  • Replace serde_cbor with ciborium

    Replace serde_cbor with ciborium

    serde_cbor is no longer maintained since August (see the repository README and RUSTSEC-2021-0127). I suggest replacing it with ciborium which is actively maintained and recommended by the author of serde_cbor as an alternative.

    However, ciborium has made different design choices that make it incompatible in some cases with serde_cbor. This may cause errors if the client and server does not use the same version of remoc.

    opened by baptiste0928 4
  • Question regarding test example

    Question regarding test example

    I have been experimenting howto create a persistent service. See https://github.com/janjaapbos/remoc-test

    I managed to achieve some results with this example.

    In one terminal: cargo run --bin server_owner

    In another terminal repeatedly: cargo run --bin client

    Does this approach make sense at all? What would you advise for best practice?

    Originally posted by @janjaapbos in https://github.com/ENQT-GmbH/remoc/discussions/1#discussioncomment-1692799

    opened by janjaapbos 3
  • Improve CI workflow

    Improve CI workflow

    Hello! I noticed that your current CI workflow takes a lot of time to run. This PR improves it:

    • Each step has been separated in different jobs (with matrix jobs for tests). This allows to run task concurrency, and makes the workflow easier to understand.
    • Dependencies are cached with Swatinem/rust-cache and baptiste0928/cargo-install to speed up workflows.
    • Clippy is also run on tests (I fixed few lints)

    image

    With these improvements, the workflow takes only 12 minutes to complete, vs. half an hour with the current one.

    This PR contains a lot of useless commits, I suggest you to squash-merge the PR instead of a regular merge.

    opened by baptiste0928 2
  • Publish releases on GitHub

    Publish releases on GitHub

    I noticed that release changelog is only available in the CHANGELOG.md file. I think it would be nice to publish them as GitHub releases too, as this allows you to quickly see when the last version was made and therefore if the project is active. It also allows people interested in remoc to watch releases and get notified when a new version is available.

    opened by baptiste0928 1
  • `rch::mpsc::Distributor` leads to easy loss of messages

    `rch::mpsc::Distributor` leads to easy loss of messages

    First, thanks for this great crate! It seems like it is exactly the abstraction I needed.

    I'm currently refactoring some code to using remoc. However, one of my test cases was spuriously failing. I tracked it down to my usage of the Distributor API. A very simplified example looks like this:

      use remoc::rch::mpsc::{self, channel};
    
      pub type Sender<T> = mpsc::Sender<T, remoc::codec::Bincode>;
      pub type Receiver<T> = mpsc::Receiver<T, remoc::codec::Bincode>;
    
      async fn send_task(sender: Sender<()>) {
          for _ in 0..16 {
              sender.send(()).await.unwrap();
          }
      }
    
      async fn receive_task(receiver: Receiver<()>) {
          let distributor = receiver.distribute(false);
          for _ in 0..16 {
              let (mut receiver, handle) = distributor.subscribe().await.unwrap();
              receiver.recv().await.unwrap().unwrap();
              dbg!("Received value");
              handle.remove();
          }
      }
    
      #[cfg(test)]
      mod tests {
          use super::*;
    
          #[tokio::test]
          async fn lost_messages() {
              let (sender, receiver) = channel(64);
              tokio::join!(send_task(sender), receive_task(receiver));
          }
      }
    

    The test case fails with a panic on the second unwrap of receiver.recv().await.unwrap().unwrap(); in the receive_task after a few iterations. I've looked at the source code and it seems, that the distributor continuously tries to get a permit for one of the DistributedReceivers and then recvs a message on the original receiver which is sent to the permit.

    I expected the Distributor to effectively turn the mpsc channel into a mpmc channel. But the behaviour is different in a crucial way. With an mpmc channel like the one offered by async_channel items can't be lost by receivers for which recv is not called.

    Example of using async_channel
        use async_channel::{Receiver, Sender};
    
        async fn send_task(sender: Sender<()>) {
            for _ in 0..16 {
                sender.send(()).await.unwrap();
            }
        }
    
        async fn receive_task(receiver: Receiver<()>) {
            for _ in 0..16 {
                let receiver_clone = receiver.clone();
                receiver_clone.recv().await.unwrap();
                dbg!("Received value");
            }
        }
    
        #[cfg(test)]
        mod tests {
            use super::*;
    
            #[tokio::test]
            async fn no_lost_messages() {
                let (sender, receiver) = async_channel::bounded(64);
                tokio::join!(send_task(sender), receive_task(receiver));
            }
        }
    

    I'm not sure how or if this behaviour could be changed. If this is the expected behaviour of the distributor, it should probably be clarified in the docs.

    I've created a repo here with the code samples to reproduce.

    opened by robinhundt 1
  • Using remoc on wasm fails due to unsupported tokio::time::Instant

    Using remoc on wasm fails due to unsupported tokio::time::Instant

    When trying to use remoc on browser, it panics because it calls tokio::time::timeout, which is based on tokio::time::Instant::now, which in turns calls std::time::Instant, which is not supported on browser. Could you consider replacing it with an implementation that supports both tokio::time (on supported platforms) and wasm time (on browsers)? For instance, please take a look at https://crates.io/crates/instant implementation.

    enhancement 
    opened by giacomocariello 1
  • Why is `Client` not `Clone` when method takes &mut self?

    Why is `Client` not `Clone` when method takes &mut self?

    Question above :) Hope this is a good place to ask!

    My use case: I have an remoc::rtc server and I want to use the client-side connection from multiple places in the code. As long as the trait has only methods with a &self receiver, the Client is Clone and so I can just .clone() it to share access to it. If I have &mut self methods, then it's not Clone any more... now I'm thinking... does this mean that the Client impl does synchronization on the client side? I would have expect the server to be solely responsible for synchronizing access to its &mut self methods, is that not the case?

    EDIT: I also cannot find where in the code it gets decided whether or not the Client will be made Clone, as well as the actual impl of the client. But I'm not used to working with heavy macro usage, so I'm sure I'm just missing the obvious :) EDIT 2: Found where it's determined, don't understand why just yet.

    And thanks for making this create, it's quite awesome! Hoping to contribute something of value to it once I've got it in production. e.g. I'm noticing that CallError is not user-extensible, so I can't pass along my own errors from rtc methods, which seems unnecessarily limiting (except that of course each feature needs effort in terms of thinking + impl + maintenance!).

    opened by seeekr 4
  • Add benchmarks

    Add benchmarks

    I think it might be interesting to add benchmarks to get an idea of remoc performance and avoid regressions. This could be done with criterion since it supports async benchmarks.

    Benchmarks could also be made to compare remoc to other similar libs like tarpc and with a raw TCP socket (to get an idea of the performance overhead introduced by remoc).

    enhancement 
    opened by baptiste0928 5
  • Built-in servers and clients for common transports

    Built-in servers and clients for common transports

    Hello! While using Remoc in my application, I realized that some connection logic was duplicated:

    • I need to implement some basic connection logic each time I create a server/client type. This includes for example graceful shutdown or automatic reconnection.
    • I use Remoc to communicate between multiple services, where each service handles a single task. Therefore, all my Remoc servers send a channel or a rtc client as soon as the connection is created. Other channels may be created later, but the base channel is only used to send a more appropriate type.

    I think these use cases are quite common, so I propose to include types to make their implementation easier:

    • Allow to initialize a connection with other types than base channel, for example directly with an RPC server/client or a broadcast channel. Maybe create Handler traits with methods that will be called directly after a connection is established.
    • Implement generic Client and Server types that are configured with a specific transport and handler, and manage the connection. This includes graceful shutdown and automatic reconnection, for example.
    • Abstract transports with a Transport trait, that could be used in Connect or directly with a Server/Client. Implementation for common transports such as TCP may be provided.

    Built-in handlers for common usages such as RPC or broadcasting stream may be provided.

    I think adding features like this would make remoc much easier to use by hiding some things that may seem complex to beginners, without sacrificing the flexibility of the library, since all these features would be optional, and could even be used partially. Remoc is a project that can be useful to many people and fulfills many use cases. Making it easier to get started with would in my opinion make it much more used and would make it a serious alternative to tarpc or tonic.

    enhancement 
    opened by baptiste0928 5
Releases(v0.10.0)
  • v0.10.0(May 25, 2022)

    Added

    • move remotely observable collections from remoc-obs crate into robs module
    • rch::watch::Receiver::send_modify method
    • chmux errors can now be converted into std::io::Error

    Changed

    • minimum supported Rust version (MSRV) is 1.59
    • remove rch::buffer types and use const generics directly to specify buffer sizes of received channel halves
    • update uuid to 1.0

    Fixed

    • fix infinite recursion in std::fmt::Debug implementation on some types
    Source code(tar.gz)
    Source code(zip)
  • v0.9.16(Feb 24, 2022)

  • v0.9.15(Feb 24, 2022)

    Changed

    • optimize default configuration for higher throughput

    Added

    • configuration defaults optimized for low memory usage or high throughput
    • enhanced configuration documentation
    Source code(tar.gz)
    Source code(zip)
  • v0.9.14(Feb 24, 2022)

  • v0.9.13(Feb 24, 2022)

    Added

    • ConnectExt trait that allows for replacement of the base channel by another object, such as an RTC client or remote broadcast channel
    • RTC example in examples/rtc

    Changed

    • optimized CI by baptiste0928
    • updated rmp-serde to 1.0
    Source code(tar.gz)
    Source code(zip)
  • v0.9.12(Feb 24, 2022)

  • v0.9.11(Feb 24, 2022)

    Added

    • conversions between remote channel receive errors
    • error message when trying to use lifetimes or function generics in a remote trait
    Source code(tar.gz)
    Source code(zip)
  • v0.9.10(Feb 24, 2022)

    Added

    • Cbor codec using ciborium, contributed by baptiste0928

    Deprecated

    • legacy Cbor codec using serde_cbors or function generics in a remote trait
    Source code(tar.gz)
    Source code(zip)
  • v0.9.9(Feb 24, 2022)

    Added

    • rch::mpsc::Receiver implements the Stream trait
    • ReceiverStream for rch::broadcast::Receiver and rch::watch::Receiver
    • rch::watch::Sender::send_replaceg serde_cbors or function generics in a remote trait
    Source code(tar.gz)
    Source code(zip)
  • v0.9.8(Feb 24, 2022)

  • v0.9.7(Feb 24, 2022)

    Added

    • rch::mpsc::Receiver::try_recv, error and take_error
    • rch::mpsc::Sender::closed_reason
    • full-codecs crate feature to activate all codecs

    Changed

    • An mpsc channel receiver will hold back a receive error due to connection failure if other senders are still active. The error will be returned after all other senders have been disconnected.
    • Fixes premature drop of RwLock owners.
    Source code(tar.gz)
    Source code(zip)
  • v0.9.6(Feb 24, 2022)

  • v0.9.5(Feb 24, 2022)

    Added

    • rtc::Client trait implemented by all generated clients. This allows to receive notifications when the server has been dropped or disconnected.
    • configuration options for transport queue lengths

    Changed

    • fix mpsc channel close notifications not being delivered sometimes
    Source code(tar.gz)
    Source code(zip)
  • v0.9.4(Feb 24, 2022)

  • v0.9.3(Feb 24, 2022)

  • v0.9.2(Feb 24, 2022)

  • v0.9.1(Feb 24, 2022)

  • v0.9.0(Feb 24, 2022)

    Added

    • is_final() on channel error types

    Changed

    • terminate providers and RTC servers when a final receive error occurs

    Removed

    • chmux::Cfg::trace_id because using tracing spans makes it redundant
    Source code(tar.gz)
    Source code(zip)
  • v0.8.2(Feb 24, 2022)

  • v0.8.1(Feb 24, 2022)

  • v0.8.0(Feb 24, 2022)

  • open-source-release(Feb 24, 2022)

Owner
ENQT GmbH
ENQT develops high-performance measurement equipment for RF networks.
ENQT GmbH
A stack for rust trait objects that minimizes allocations

dynstack A stack for trait objects that minimizes allocations COMPATIBILITY NOTE: dynstack relies on an underspecified fat pointer representation. Tho

Gui Andrade 114 Nov 28, 2022
A stack-allocated box that stores trait objects.

This crate allows saving DST objects in the provided buffer. It allows users to create global dynamic objects on a no_std environment without a global allocator.

Aleksey Sidorov 19 Dec 13, 2022
๐Ÿ“ฎ load, write, and copy remote and local assets

axoasset This library offers read, write, and copy functions, for local and remote assets given a string that contains a relative or absolute local pa

axo 7 Jan 25, 2023
A self-contained, single-binary Rust and Leptos application for remote Wake-on-LAN

Remote Wake-on-LAN with Rust and Leptos A self-contained, single-binary Rust and Leptos application serving a web interface to wake another device on

Valentin Bersier 6 Jan 28, 2023
๐Ÿšง (Alpha stage software) Binary that supports remote filesystem and process operations. ๐Ÿšง

distant Binary to connect with a remote machine to edit files and run programs. ?? (Alpha stage software) This program is in rapid development and may

Chip Senkbeil 296 Dec 28, 2022
Mobile safari / webview remote debugging and e2e testing libraries

Canter (WIP) (WIP) Mobile safari / webview remote debugging and e2e testing libraries. Developed for safari/webview e2e testing on iPhone. Works only

Han Lee 9 Aug 16, 2022
M8 remote display - Rust

RM8 Remote display for the Dirtywave M8 I first tried M8WebDisplay then discovered m8c and decided to use it as a starting point for my own version in

Alex Boussinet 30 Dec 31, 2022
๐ŸŒฒ Open the current remote repository in your browser

gitweb Some of the flags and options are subject to change in the future. Ideas are welcome. Ideas are bulletproof (V). gitweb is a command line inter

Yoann Fleury 26 Dec 17, 2022
Remote Secret Editor for AWS Secret Manager

Barberousse - Remote Secrets Editor About Usage Options Printing Editing Copying RoadMap 1.0 1.1 Future About A project aimed to avoid downloading sec

Mohamed Zenadi 18 Sep 28, 2021
Incremental, multi-version remote backup tool for block devices.

bsync Incremental, multi-version remote backup tool for block devices. The on-disk backup format is a SQLite database and I've been dogfooding this on

Heyang Zhou 7 Aug 21, 2022
The system for remote workers to prevent their family members from interrupting conference calls

onair The system for remote workers to prevent their family members from interrupting conference calls. The system is designed to automatically detect

Yushi OMOTE 6 Sep 21, 2022
Fusion is a cross-platform App Dev ToolKit build on Rust . Fusion lets you create Beautiful and Fast apps for mobile and desktop platform.

Fusion is a cross-platform App Dev ToolKit build on Rust . Fusion lets you create Beautiful and Fast apps for mobile and desktop platform.

Fusion 1 Oct 19, 2021
A Diablo II library for core and simple client functionality, written in Rust for performance, safety and re-usability

A Diablo II library for core and simple client functionality, written in Rust for performance, safety and re-usability

null 4 Nov 30, 2022
Code examples, data structures, and links from my book, Rust Atomics and Locks.

This repository contains the code examples, data structures, and links from Rust Atomics and Locks. The examples from chapters 1, 2, 3, and 8 can be f

Mara Bos 338 Jan 6, 2023
List of Persian Colors and hex colors for CSS, SCSS, PHP, JS, Python, and Ruby.

Persian Colors (Iranian colors) List of Persian Colors and hex colors for CSS, SCSS, PHP, C++, QML, JS, Python, Ruby and CSharp. Persian colors Name H

Max Base 12 Sep 3, 2022
Northstar is a horizontally scalable and multi-tenant Kubernetes cluster provisioner and orchestrator

Northstar Northstar is a horizontally scalable and multi-tenant Kubernetes cluster provisioner and orchestrator. Explore the docs ยป View Demo ยท Report

Lucas Clerisse 1 Jan 22, 2022
Time related types (and conversions) for scientific and astronomical usage.

astrotime Time related types (and conversions) for scientific and astronomical usage. This library is lightweight and high performance. Features The f

Michael Dilger 3 Aug 22, 2022
UnTeX is both a library and an executable that allows you to manipulate and understand TeX files.

UnTeX UnTeX is both a library and an executable that allows you to manipulate and understand TeX files. Usage Executable If you wish to use the execut

Jรฉrome Eertmans 1 Apr 5, 2022
A convenient tracing config and init lib, with symlinking and local timezone.

clia-tracing-config A convenient tracing config and init lib, with symlinking and local timezone. Use these formats default, and can be configured: pr

Cris Liao 5 Jan 3, 2023