{"id":24482876,"url":"https://github.com/tylerbloom/troupe","last_synced_at":"2025-04-13T16:43:31.689Z","repository":{"id":183092394,"uuid":"669586945","full_name":"TylerBloom/troupe","owner":"TylerBloom","description":"Library for modelling Rust applications with actors","archived":false,"fork":false,"pushed_at":"2025-03-31T16:39:08.000Z","size":139,"stargazers_count":17,"open_issues_count":9,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-10T08:11:57.379Z","etag":null,"topics":["actor-model","concurrent-data-structure","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-2.1","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TylerBloom.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-22T19:02:38.000Z","updated_at":"2025-04-08T10:08:14.000Z","dependencies_parsed_at":"2024-02-29T18:53:55.199Z","dependency_job_id":"d6b4e877-36bc-4e9b-bc02-5f1d2a592b08","html_url":"https://github.com/TylerBloom/troupe","commit_stats":null,"previous_names":["tylerbloom/troupe"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TylerBloom%2Ftroupe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TylerBloom%2Ftroupe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TylerBloom%2Ftroupe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TylerBloom%2Ftroupe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TylerBloom","download_url":"https://codeload.github.com/TylerBloom/troupe/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248747561,"owners_count":21155473,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actor-model","concurrent-data-structure","rust"],"created_at":"2025-01-21T12:15:47.983Z","updated_at":"2025-04-13T16:43:31.669Z","avatar_url":"https://github.com/TylerBloom.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Crates.io](https://img.shields.io/crates/v/troupe.svg)](https://crates.io/crates/troupe)\n[![Documentation](https://docs.rs/troupe/badge.svg)](https://docs.rs/troupe/)\n![GitHub Workflows](https://github.com/TylerBloom/troupe/actions/workflows/ci.yml/badge.svg)\n[![Coverage Status](https://codecov.io/gh/TylerBloom/troupe/branch/main/graph/badge.svg)](https://codecov.io/gh/TylerBloom/troupe)\n![Maintenance](https://img.shields.io/badge/Maintenance-Actively%20Developed-brightgreen.svg)\n\n# About\nTroupe is an experimental, high-level library for modeling your application using the [actor model](https://en.wikipedia.org/wiki/Actor_model).\nActors model concurrent mutable state via isolated components and message passing.\nThis approach enables you to both have clear seperation of concerns between different components of your program as well as plainly model how a component can change.\nAn actor approach is strongest when:\n  - there are multiple independent sources of mutation in your application\n  - you want to model the data flow through part or all of your system\n  - you want to want to build your system from a series of distinct components that can evolve independently\n\nTroupe was born while building full-stack Rust apps.\nThere, the frontend client authenticates, opens a websocket connection, pulls data from the server, and presents a UI to the user.\nIn the single-threaded envinorment of WASM, it is difficult to manage all of the sources of mutation: UI interactions, updates from the server, etc.\nThere, actors can be used to manage the websocket connection, process data from the server, and propagate it to the rest of the app (including non-actor parts like the UI).\nThe UI can then communicate with the actors to present the UI and process interactions.\nOn the server side, actors can be used to manage websockets, buffer communicate between the database and cached data, and track changes in a user's session info.\n\nTo achieve isolation, actors in `troupe` are contained within an async processes of existing async runtimes.\nBecause of this, `troupe` is able to support several async runtimes, including `tokio`, `async-std`, and `wasm-bindgen-futures` (for WASM targets), which allows you to slowly incorporate it into your project's architecture.\nDo note that actor communication uses `tokio`'s channels regardless of the async runtime.\n\n# Model\nThe heart of a `troupe` actor is a state type and a message type.\nLet's take a simple cache as an example:\n```rust\npub struct Cache(HashMap\u003cUuid, YourData\u003e);\n\npub enum CacheCommand {\n    Insert(Uuid, YourData),\n    Get(Uuid, OneshotSender\u003cOption\u003cYourData\u003e\u003e),\n    Delete(Uuid),\n}\n```\n\nThe message type encapsulates how the state can change and what data the actor has to process. These messages are sent from various source to be processed by the state. To finish the actor, `Cache` just has to implement the `ActorState` trait.\n```rust\nimpl ActorState for Cache {\n    type Permanence = Permanent;\n    type ActorType = SinkActor;\n\n    type Message = CacheCommand;\n    type Output = ();\n\n    async fn process(\u0026mut self, _: \u0026mut Scheduler\u003cSelf\u003e, msg: CacheCommand) {\n        match msg {\n            CacheCommand::Insert(key, val) =\u003e {\n                self.inner.insert(key, val);\n            }\n            CacheCommand::Get(key, send) =\u003e {\n                let _ = send.send(self.inner.get(\u0026key).cloned());\n\t\t\t\t\t\t}\n            CacheCommand::Delete(key) =\u003e {\n                self.inner.remove(\u0026key);\n            }\n        }\n    }\n}\n```\n\n`ActorState` has several other associated types beyond the message type.\nThese types are mostly marker types and inform how other parts of your program should interact with the actor.\nThis communication is done through a client, which is created when the actor is launched.\nThe associated `ActorType` type tells the client if the actor expects messages to be sent to it or if messages will be broadcast from the actor (or both).\nThe associated `Permanence` type informs the client if it should expect the actor to close at any point.\nLastly, the associated `Output` type is only used for actor that broadcast messages, in which case the actor will broadcast messages of the `Output` type.\n\nOnce running, `troupe` pairs every actor state with a scheduler.\nThe scheduler is responsible for managing futures that the state queues and attached streams of message.\nThe queued futures will be polled at the same time that the scheduler waits for inbound messages.\nMost actors have one attached stream by default, the channel used to communicate between the client and the actor.\nClient message streams use tokio MPSC-style channels, but actors can add any stream that yield messages that can be converted into the actor's message type (such as socket connections).\nConceptually, the scheduler-actor relationship can be model as:\n```\n _____________________________________________\n|                    Actor                    |\n|  ___________                   ___________  |\n| | Scheduler | --- message --\u003e |   State   | |\n| | [streams] | \u003c-- streams --- |           | |\n| | [futures] | \u003c-- futures --- |           | |\n| |___________|                 |___________| |\n|_____________________________________________|\n```\n\nAs stated before, every actor returns a client.\nEach actor defines how its clients behave.\nThere are three types of clients: `SinkClient`, `StreamClient`, and `JointClient`.\n\nA `SinkClient` is the type of client you are most likely to use.\nIt enables you to send messages directly to the actor.\nThese messages can be either \"fire-and-forget\" or \"request-response\" style messages.\nNote that a `SinkClient` does not actually implement the [`Sink`](https://docs.rs/futures/latest/futures/sink/trait.Sink.html) trait, but, rather, serves the same conceptual purpose.\nA `StreamClient` listens for messages that are broadcast from an actor.\nUnlike a `SinkClient`, a `StreamClient` does not directly support any type of message passing into the actor.\nIt does, however, implement the [`Stream`](https://docs.rs/futures/latest/futures/stream/trait.Stream.html) trait.\nLastly, a `JointClient` is both a `SinkClient` and a `StreamClient` put together.\nA `JointClient` can be decomposed into one or both of the other clients.\nNote, the actor type defines what kind of clients can be constructed, so you can not, for example, construct a `StreamClient` for an actor that will never broadcast a message.\n\nThe last major component of the `troupe` actor model is permanence.\nSome actors are designed to run forever.\nThese are called `Permanent` actors.\nOther actors are designed to run for some time (perhaps a long time) and then close.\nThese are called `Transient` actors.\nThis distinction largely serves to help with the ergonomics of interacting with actors.\nIf an actor is designed to never shutdown, then the oneshot channels used for request-response style messages can be safely unwrapped.\nThe same is not true for `Transient` actors.\n\nRegardless of the permanence of the actor, all actors might exhaust their source of messages.\nThis happens when all streams have ran dry and no message-yeilding futures are queued.\nWhen this happens, there is built-in \"garbage collection\" for troupe actors.\nThe scheduler will mark an actor as \"dead\" and then shutdown the actor process if it ever reaches this state.\nFor `SinkActors`, this can only occur if all message-sending clients have been dropped.\n\n# Backwards Compatibility\nTroupe is currently experimental and subject to potential breaking changes (with due consideration).\nBreaking changes might occur to improve API ergonomics, to tweak the actor model, or to use new Rust language features.\nIn particular, there are several language features that will be used improve this crate upon stabilization:\n - [`async fn` in traits](https://rust-lang.github.io/async-book/07_workarounds/05_async_in_traits.html)\n - [specialization](https://rust-lang.github.io/rfcs/1210-impl-specialization.html)\n - [associate-type defaults](https://rust-lang.github.io/rfcs/2532-associated-type-defaults.html)\n\nThe stabilization of the first two present garunteed breaking changes for this crate but will drastically improve usability and ergonomics.\nSpecialization will enable the `ActorBuilder` to present identically-named methods for launching the actor while returning the appropiate client type.\nThe stabilization of `async fn` in traits will allow for the loosening of constraints on `ActorState`s in WASM contexts, allowing them to just be `'static` instead of `'static + Send`.\n\n# Future Work\nCurrently, `troupe` only provides a framework for building actors.\nIf there are certain patterns that are commonplace, a general version of that pattern might find its way into this crate or a similar crate.\nOne such example is something like a local-first buffered state.\nHere, you have some data that exists in two locations, say client and server, and you want update the state in one location then propagate those changes elsewhere.\n\nAnother possible actor pattern in a \"poll\" actor.\nThis would build upon a broadcast actor, but the broadcast message would contain a oneshot channel for communicating a \"vote\" with the actor.\n\n\n# Usage and Licensing\nThis project is currently licensed under a LGPL-2.1 license.\nThis allows you to use this crate to build other crates (library or application) under any license (open-source or otherwise).\nThe primary intent of using this license is to require crates that modify to source of this crate to also be open-source licensed (LGPL or stronger).\n\n# Contributing\nIf there is an aspect of the project that you wish to see changed, improved, or even removed, open a ticket or PR.\nIf you have an interesting design pattern that uses actors and would like to see if in this crate, open a ticket!!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftylerbloom%2Ftroupe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftylerbloom%2Ftroupe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftylerbloom%2Ftroupe/lists"}