{"id":16912486,"url":"https://github.com/jakmeier/nuts","last_synced_at":"2025-03-17T07:30:59.876Z","repository":{"id":49544530,"uuid":"287382135","full_name":"jakmeier/nuts","owner":"jakmeier","description":"Nuts is a Rust library that offers a simple publish-subscribe API, featuring decoupled creation of the publisher and the subscriber.","archived":false,"fork":false,"pushed_at":"2021-06-15T08:25:06.000Z","size":301,"stargazers_count":67,"open_issues_count":2,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-02-27T19:35:11.830Z","etag":null,"topics":["publish-subscribe","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jakmeier.png","metadata":{"files":{"readme":"README.md","changelog":"changes.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-13T21:20:25.000Z","updated_at":"2024-12-11T16:17:36.000Z","dependencies_parsed_at":"2022-09-08T00:01:20.923Z","dependency_job_id":null,"html_url":"https://github.com/jakmeier/nuts","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakmeier%2Fnuts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakmeier%2Fnuts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakmeier%2Fnuts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakmeier%2Fnuts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jakmeier","download_url":"https://codeload.github.com/jakmeier/nuts/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243848104,"owners_count":20357491,"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":["publish-subscribe","rust"],"created_at":"2024-10-13T19:10:15.640Z","updated_at":"2025-03-17T07:30:59.562Z","avatar_url":"https://github.com/jakmeier.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- Auto generated by build.rs + README_TEMPLATE.md --\u003e\n# Nuts\n\nNuts is a library that offers a simple publish-subscribe API, featuring decoupled creation of the publisher and the subscriber.\n\n## Quick first example\n```rust\nstruct Activity;\nlet activity = nuts::new_activity(Activity);\nactivity.subscribe(\n    |_activity, n: \u0026usize|\n    println!(\"Subscriber received {}\", n)\n);\nnuts::publish(17usize);\n// \"Subscriber received 17\" is printed\nnuts::publish(289usize);\n// \"Subscriber received 289\" is printed\n```\n\nAs you can see in the example above, no explicit channel between publisher and subscriber is necessary.\nThe call to `publish` is a static method that requires no state from the user.\nThe connection between them is implicit because both use `usize` as message type.\n\nNuts enables this simple API by managing all necessary state in thread-local storage.\nThis is particularly useful when targeting the web. However, Nuts can be used on other platforms, too.\nIn fact, Nuts has no dependencies aside from std.\n\n## State of Library\nWith the release of Nuts version 0.2 on [crates.io](https://crates.io/crates/nuts), it has reached an important milestone.\nThe single-threaded features have all been implemented. Maybe a method here and there needs to be added. But I would not expect to go through major API overhauls again in the existing interface at this point.\n\nThere is one big pending feature left, however. This is parallel dispatch, covered in [#2](https://github.com/jakmeier/nuts/issues/2).\nIdeally, that would be implemented under the hood. But likely it will make sense to add some more methods to the API.\n\nIf and when parallel dispatch get implemented, Nuts probably looks at a stable 1.0 release.\n\n## Activities\n\nActivities are at the core of Nuts.\nFrom the globally managed data, they represent the active part, i.e. they can have event listeners.\nThe passive counter-part is defined by `DomainState`.\n\nEvery struct that has a type with static lifetime (anything that has no lifetime parameter that is determined only at runtime) can be used as an Activity.\nYou don't have to implement the `Activity` trait yourself, it will always be automatically derived if possible.\n\nTo create an activity, simply register the object that should be used as activity, using `nuts::new_activity` or one of its variants.\n\nIt is important to understand that Activities are uniquely defined by their type.\nYou cannot create two activities from the same type. (But you can, for example, create a wrapper type around it.)\nThis allows activities to be referenced by their type, which must be known at run-time.\n\n## Publish\n\nAny instance of a struct or primitive can be published, as long as its type is known at compile-time. (The same constraint as for Activities.)\nUpon calling `nuts::publish`, all active subscriptions for the same type are executed and the published object will be shared with all of them.\n\n### Example\n```rust\nstruct ChangeUser { user_name: String }\npub fn main() {\n    let msg = ChangeUser { user_name: \"Donald Duck\".to_owned() };\n    nuts::publish(msg);\n    // Subscribers to messages of type `ChangeUser` will be notified\n}\n```\n\n## Subscribe \nActivities can subscribe to messages, based on the Rust type identifier of the message. Closures or function pointers can be used to create a subscription for a specific type of messages.\n\n\nNuts uses `core::any::TypeId` internally to compare the types. Subscriptions are called when the type of a published message matches the message type of the subscription.\n\nThere are several different methods for creating new subscriptions. The simplest of them is simply called `subscribe(...)` and it can be used like this:\n\n\n```rust\nstruct MyActivity { id: usize };\nstruct MyMessage { text: String };\n\npub fn main() {\n    let activity = nuts::new_activity(MyActivity { id: 0 } );\n    activity.subscribe(\n        |activity: \u0026mut MyActivity, message: \u0026MyMessage|\n        println!(\"Subscriber with ID {} received text: {}\", activity.id, message.text)\n    );\n}\n```\nIn the example above, a subscription is created that waits for messages of type `MyMessage` to be published.\nSo far, the code inside the closure is not executed and nothing is printed to the console.\n\nNote that the first argument of the closure is a mutable reference to the activity object.\nThe second argument is a read-only reference to the published message.\nBoth types must match exactly or otherwise the closure will not be accepted by the compiler.\n\nA function with the correct argument types can also be used to subscribe.\n```rust\nstruct MyActivity { id: usize };\nstruct MyMessage { text: String };\n\npub fn main() {\n    let activity = nuts::new_activity(MyActivity { id: 0 } );\n    activity.subscribe(MyActivity::print_text);\n}\n\nimpl MyActivity {\n    fn print_text(\u0026mut self, message: \u0026MyMessage) {\n        println!(\"Subscriber with ID {} received text: {}\", self.id, message.text)\n    }\n}\n```\n\n## Example: Basic Activity with Publish + Subscribe\n\n```rust\n#[derive(Default)]\nstruct MyActivity {\n    round: usize\n}\nstruct MyMessage {\n    no: usize\n}\n\n// Create activity\nlet activity = MyActivity::default();\n// Activity moves into globally managed state, ID to handle it is returned\nlet activity_id = nuts::new_activity(activity);\n\n// Add event listener that listens to published `MyMessage` types\nactivity_id.subscribe(\n    |my_activity, msg: \u0026MyMessage| {\n        println!(\"Round: {}, Message No: {}\", my_activity.round, msg.no);\n        my_activity.round += 1;\n    }\n);\n\n// prints \"Round: 0, Message No: 1\"\nnuts::publish( MyMessage { no: 1 } );\n// prints \"Round: 1, Message No: 2\"\nnuts::publish( MyMessage { no: 2 } );\n```\n\n## Example: Private Channels\nIn what I have shown you so far, all messages have been shared reference and it is sent to all listeners that registered to a specific message type.\nAn alternative is to use private channels. A sender can then decide which listening activity will receive the message.\nIn that case, the ownership of the message is given to the listener.\n\n```rust\nstruct ExampleActivity {}\nlet id = nuts::new_activity(ExampleActivity {});\n// `private_channel` works similar to `subscribe` but it owns the message.\nid.private_channel(|_activity, msg: usize| {\n    assert_eq!(msg, 7);\n});\n// `send_to` must be used instead of `publish` when using private channels.\n// Which activity receives the message is decide by the first type parameter.\nnuts::send_to::\u003cExampleActivity, _\u003e(7usize);\n```\n\n## Activity Lifecycle\n\nEach activity has a lifecycle status that can be changed using [`set_status`](https://docs.rs/nuts/0.2.1/nuts/struct.ActivityId.html#method.set_status).\nIt starts with `LifecycleStatus::Active`.\nIn the current version of Nuts, the only other status is `LifecycleStatus::Inactive`.\n\nThe inactive status can be used to put activities to sleep temporarily.\nWhile inactive, the activity will not be notified of events it has subscribed to.\nA subscription filter can been used to change this behavior.\n(See [`subscribe_masked`](https://docs.rs/nuts/0.2.1/nuts/struct.ActivityId.html#method.subscribe_masked))\n\nIf the status of a changes from active to inactive, the activity's [`on_leave`](https://docs.rs/nuts/0.2.1/nuts/struct.ActivityId.html#method.on_leave) and [`on_leave_domained`](https://docs.rs/nuts/0.2.1/nuts/struct.ActivityId.html#method.on_leave_domained) subscriptions will be called.\n\nIf the status of a changes from inactive to active, the activity's [`on_enter`](https://docs.rs/nuts/0.2.1/nuts/struct.ActivityId.html#method.on_enter) and [`on_enter_domained`](https://docs.rs/nuts/0.2.1/nuts/struct.ActivityId.html#method.on_enter_domained) subscriptions will be called.\n\n\n## Domains\n\n\nA Domain stores arbitrary data for sharing between multiple [Activities](https://docs.rs/nuts/0.2.1/nuts/trait.Activity.html).\nLibrary users can define the number of domains but each activity can only join one domain.\n\nDomains should only be used when data needs to be shared between multiple activities of the same or different types.\nIf data is only used by a single activity, it is usually better to store it in the activity struct itself.\n\nIn case only one domain is used, you can also consider to use [`DefaultDomain`](https://docs.rs/nuts/0.2.1/nuts/struct.DefaultDomain.html) instead of creating your own enum.\n\nFor now, there is no real benefit from using multiple Domains, other than data isolation.\nBut there are plans for the future that will schedule Activities in different threads, based on their domain.\n\n### Creating Domains\nNuts creates domains implicitly in the background. The user can simply provide an enum or struct that implements the `DomainEnumeration` trait. This trait requires only the `fn id(\u0026self) -\u003e usize` function, which maps every object to a number representing the domain.\n\nTypically, domains are defined by an enum and the `DomainEnumeration` trait is derived using using [`domain_enum!`](macro.domain_enum.html). \n\n\n```\n#[macro_use] extern crate nuts;\nuse nuts::{domain_enum, DomainEnumeration};\n#[derive(Clone, Copy)]\nenum MyDomain {\n    DomainA,\n    DomainB,\n}\ndomain_enum!(MyDomain);\n```\n\n### Using Domains\nThe function [`nuts::store_to_domain`](fn.store_to_domain.html) allows to initialize data in a domain. Afterwards, the data is available in subscription functions of the activities.\n\n\nOnly one instance per type id can be stored inside a domain.\nIf an old value of the same type already exists in the domain, it will be overwritten.\n\nIf activities are associated with a domain, they must be registered using the [`nuts::new_domained_activity`](fn.new_domained_activity.html).\nThis will allow to subscribe with closures that have access to domain state.\n[`subscribe_domained`](struct.ActivityId.html#method.subscribe_domained) is used to add those subscriptions.\n[`subscribe`](struct.ActivityId.html#method.subscribe) can still be used for subscriptions that do not access the domain.\n\n#### Example of Activity with Domain\n\n```rust\nuse nuts::{domain_enum, DomainEnumeration};\n\n#[derive(Default)]\nstruct MyActivity;\nstruct MyMessage;\n\n#[derive(Clone, Copy)]\nenum MyDomain {\n    DomainA,\n    DomainB,\n}\ndomain_enum!(MyDomain);\n\n// Add data to domain\nnuts::store_to_domain(\u0026MyDomain::DomainA, 42usize);\n\n// Register activity\nlet activity_id = nuts::new_domained_activity(MyActivity, \u0026MyDomain::DomainA);\n\n// Add event listener that listens to published `MyMessage` types and has also access to the domain data\nactivity_id.subscribe_domained(\n    |_my_activity, domain, msg: \u0026MyMessage| {\n        // borrow data from the domain\n        let data = domain.try_get::\u003cusize\u003e();\n        assert_eq!(*data.unwrap(), 42);\n    }\n);\n\n// make sure the subscription closure is called\nnuts::publish( MyMessage );\n```\n\n## Advanced: Understanding the Execution Order\n\nWhen calling `nuts::publish(...)`, the message may not always be published immediately. While executing a subscription handler from previous `publish`, all new messages are queued up until the previous one is completed.\n```rust\nstruct MyActivity;\nlet activity = nuts::new_activity(MyActivity);\nactivity.subscribe(\n    |_, msg: \u0026usize| {\n        println!(\"Start of {}\", msg);\n        if *msg \u003c 3 {\n            nuts::publish( msg + 1 );\n        }\n        println!(\"End of {}\", msg);\n    }\n);\n\nnuts::publish(0usize);\n// Output:\n// Start of 0\n// End of 0\n// Start of 1\n// End of 1\n// Start of 2\n// End of 2\n// Start of 3\n// End of 3\n```\n\n## Full Demo Examples\nA simple example using nuts to build a basic clicker game is available in [examples/clicker-game](tree/master/examples/clicker-game). It requires `wasm-pack` installed and `npm`. To run the example, execute `wasm-pack build` and then `cd www; npm install; npm run start`.\nThis example only shows minimal features of nuts.\n\nRight now, there are no more examples (some had to be removed due to outdated dependencies). Hopefully that will change at some point.\n\nAll examples are set up as their own project. (To avoid polluting the libraries dependencies.)\nTherefore the standard `cargo run --example` will not work. One has to go to the example's directory and build it from there.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakmeier%2Fnuts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjakmeier%2Fnuts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakmeier%2Fnuts/lists"}