{"id":15654562,"url":"https://github.com/seldom-se/seldom_fn_plugin","last_synced_at":"2025-04-16T05:57:28.796Z","repository":{"id":58294898,"uuid":"531007901","full_name":"Seldom-SE/seldom_fn_plugin","owner":"Seldom-SE","description":"Allows using Rust functions in place of Bevy plugins","archived":false,"fork":false,"pushed_at":"2024-02-19T08:29:54.000Z","size":27,"stargazers_count":26,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-03-15T07:59:22.092Z","etag":null,"topics":["bevy","bevy-engine","bevy-plugin","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Seldom-SE.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","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":"2022-08-31T09:02:47.000Z","updated_at":"2024-02-24T05:17:42.000Z","dependencies_parsed_at":"2024-10-03T12:52:30.281Z","dependency_job_id":"47944a07-5080-4279-9769-ddee14a307bf","html_url":"https://github.com/Seldom-SE/seldom_fn_plugin","commit_stats":{"total_commits":8,"total_committers":2,"mean_commits":4.0,"dds":0.25,"last_synced_commit":"8b58eb335b6874583710c2a7c7f17319b3a0de1f"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Seldom-SE%2Fseldom_fn_plugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Seldom-SE%2Fseldom_fn_plugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Seldom-SE%2Fseldom_fn_plugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Seldom-SE%2Fseldom_fn_plugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Seldom-SE","download_url":"https://codeload.github.com/Seldom-SE/seldom_fn_plugin/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249205757,"owners_count":21229989,"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":["bevy","bevy-engine","bevy-plugin","rust"],"created_at":"2024-10-03T12:52:28.179Z","updated_at":"2025-04-16T05:57:28.778Z","avatar_url":"https://github.com/Seldom-SE.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"## NOTE\n\nAs of Bevy 0.13, types implementing `Fn(\u0026mut App)` automatically implement `Plugin` now! For most\ncases, I recommend using that functionality. However, this can't be used to avoid cloning (see\nthe last example in this README), so this crate is still useful for that use case. But, that's\nnot enough use to justify this crate (personally, I only ran into the cloning problem once), so\nI won't update this crate for Bevy 0.14. So, if you still prefer this pattern, copy the source\ncode into your project! I'm pretty proud that this crate reached 10k downloads! That's 1,000\ndownloads per line of Rust code, excluding comments and whitespace, which is pretty extreme.\n\n# `seldom_fn_plugin`\n\n[![Crates.io](https://img.shields.io/crates/v/seldom_fn_plugin.svg)](https://crates.io/crates/seldom_fn_plugin)\n[![MIT/Apache 2.0](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/Seldom-SE/seldom_fn_plugin#license)\n[![Crates.io](https://img.shields.io/crates/d/seldom_fn_plugin.svg)](https://crates.io/crates/seldom_fn_plugin)\n\n`seldom_fn_plugin` allows using Rust functions in place of Bevy plugins\nwithout sacrificing the builder pattern. This improves the ergonomics of plugin-heavy apps\nand makes it possible to avoid certain `.clone()`s while maintaining modularity.\n\nI would advise against replacing Bevy plugins with `fn_plugin`s in a public API. It is better\nto keep consistent with the rest of the Bevy ecosystem in this case. However, you may expose both\nand just have the Bevy plugin add the `fn_plugin`.\n\nThe code for this crate is only 10 lines, excluding docs and whitespace,\nso you can avoid adding a dependency by just copying the code into your project.\nI decided to publish it despite its length for a few reasons. First,\nI want to see people use this pattern. Second, I work on many Bevy projects,\nand would like to reduce the duplication of this code. Finally, I intend to publish more crates,\nso it doesn't hurt to get familiar with the process.\n\n## Usage\n\n```toml\n# Replace * with your desired version\n\n[dependencies]\nseldom_fn_plugin = \"*\"\n```\n\nSee the examples below for example usage.\n\n## Compatibility\n\n| Bevy | `seldom_fn_plugin` |\n| ---- | ------------------ |\n| 0.13 | 0.6                |\n| 0.12 | 0.5                |\n| 0.11 | 0.4                |\n| 0.10 | 0.3                |\n| 0.9  | 0.2                |\n| 0.8  | 0.1                |\n\n## Examples\n\nHere is an example usage:\n\n```Rust\nuse bevy::prelude::*;\nuse seldom_fn_plugin::FnPluginExt;\n\nfn say_hi() {\n    println!(\"hi\");\n}\n\nfn my_plugin(app: \u0026mut App) {\n    app.add_system(say_hi);\n}\n\nfn main() {\n    App::new().fn_plugin(my_plugin).run();\n}\n```\n\nHere are some examples from a game and some other crates I'm developing:\n\nBefore:\n\n```Rust\npub struct ControlsPlugin;\n\nimpl Plugin for ControlsPlugin {\n    fn build(\u0026self, app: \u0026mut App) {\n        app.init_resource::\u003cControls\u003e();\n    }\n}\n```\n\nAfter:\n\n```Rust\npub fn controls_plugin(app: \u0026mut App) {\n    app.init_resource::\u003cControls\u003e();\n}\n```\n\nBefore:\n\n```Rust\npub(crate) struct AssetPlugin;\n\nimpl Plugin for AssetPlugin {\n    fn build(\u0026self, app: \u0026mut App) {\n        app.add_plugin(PxAssetPlugin::\u003cPxSpriteData\u003e::default())\n            .add_plugin(PxAssetPlugin::\u003cPxTilesetData\u003e::default())\n            .add_plugin(PxAssetPlugin::\u003cPxTypefaceData\u003e::default())\n            .add_plugin(PxAssetPlugin::\u003cPxFilterData\u003e::default());\n    }\n}\n\nstruct PxAssetPlugin\u003cD: PxAssetData\u003e(PhantomData\u003cD\u003e);\n\nimpl\u003cD: PxAssetData\u003e Plugin for PxAssetPlugin\u003cD\u003e {\n    fn build(\u0026self, app: \u0026mut App) {\n        app.add_asset::\u003cPxAsset\u003cD\u003e\u003e()\n            .init_resource::\u003cLoadingAssets\u003cD\u003e\u003e()\n            .add_system(D::load.in_set(PxSet::LoadAssets));\n    }\n}\n\nimpl\u003cD: PxAssetData\u003e Default for PxAssetPlugin\u003cD\u003e {\n    fn default() -\u003e Self {\n        Self(default())\n    }\n}\n```\n\nAfter (avoids an annoying `PhantomData`):\n\n```Rust\npub(crate) fn asset_plugin(app: \u0026mut App) {\n    app.fn_plugin(px_asset_plugin::\u003cPxSpriteData\u003e)\n        .fn_plugin(px_asset_plugin::\u003cPxTilesetData\u003e)\n        .fn_plugin(px_asset_plugin::\u003cPxTypefaceData\u003e)\n        .fn_plugin(px_asset_plugin::\u003cPxFilterData\u003e);\n}\n\nfn px_asset_plugin\u003cD: PxAssetData\u003e(app: \u0026mut App) {\n    app.add_asset::\u003cPxAsset\u003cD\u003e\u003e()\n        .init_resource::\u003cLoadingAssets\u003cD\u003e\u003e()\n        .add_system(D::load.in_set(PxSet::LoadAssets));\n}\n```\n\nBefore:\n\n```Rust\npub(crate) struct CollisionPlugin\u003cG: PxCollisionGroup\u003e {\n    listeners: HashMap\u003cG, HashSet\u003cG\u003e\u003e,\n    resolvers: HashMap\u003cG, HashSet\u003cG\u003e\u003e,\n}\n\nimpl\u003cG: PxCollisionGroup\u003e Plugin for CollisionPlugin\u003cG\u003e {\n    fn build(\u0026self, app: \u0026mut App) {\n        app.add_event::\u003cPxCollision\u003e().add_system(detect_collisions(\n            self.listeners.clone(),\n            self.resolvers.clone(),\n        ));\n    }\n}\n\nimpl\u003cG: PxCollisionGroup\u003e CollisionPlugin\u003cG\u003e {\n    pub(crate) fn new(\n        listeners: HashMap\u003cG, HashSet\u003cG\u003e\u003e,\n        resolvers: HashMap\u003cG, HashSet\u003cG\u003e\u003e,\n    ) -\u003e Self {\n        Self {\n            listeners,\n            resolvers,\n        }\n    }\n}\n```\n\nAfter (avoids a couple `.clone()`s):\n\n```Rust\npub(crate) fn collision_plugin\u003cG: PxCollisionGroup\u003e(\n    listeners: HashMap\u003cG, HashSet\u003cG\u003e\u003e,\n    resolvers: HashMap\u003cG, HashSet\u003cG\u003e\u003e,\n) -\u003e impl FnOnce(\u0026mut App) {\n    |app| {\n        app.add_event::\u003cPxCollision\u003e()\n            .add_system(detect_collisions(listeners, resolvers));\n    }\n}\n```\n\n## License\n\n`seldom_fn_plugin` is dual-licensed under MIT and Apache 2.0 at your option.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseldom-se%2Fseldom_fn_plugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fseldom-se%2Fseldom_fn_plugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseldom-se%2Fseldom_fn_plugin/lists"}