add event_bus_typed
This commit is contained in:
Generated
+798
@@ -0,0 +1,798 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloca"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"ciborium-ll",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-io"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-ll"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3"
|
||||
dependencies = [
|
||||
"alloca",
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"itertools",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"page_size",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"tokio",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
||||
|
||||
[[package]]
|
||||
name = "event_bus_typed"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"parking_lot",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "page_size"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tokio-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "event_bus_typed"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[workspace]
|
||||
|
||||
[lib]
|
||||
name = "event_bus_typed"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "event_bus_throughput"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.53.1", default-features = false, features = ["rt", "sync"] }
|
||||
parking_lot = "0.12.5"
|
||||
tracing = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.53.1", default-features = false, features = ["rt", "rt-multi-thread", "macros", "time", "sync"] }
|
||||
criterion = { version = "0.8.2", features = ["async_tokio"] }
|
||||
uuid = { version = "1.26.1", features = ["v4"] }
|
||||
@@ -0,0 +1,148 @@
|
||||
# event_bus_typed
|
||||
|
||||
Un bus d'événements asynchrone en mémoire pour Tokio, entièrement basé sur le typage fort en Rust (`TypeId`).
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Problématique résolue
|
||||
|
||||
Dans l'implémentation initiale (`event_bus`), le modèle était inspiré de JavaScript (topics basés sur des chaînes de caractères) :
|
||||
- Les événements étaient transportés via un pointeur générique `AnyEvent` (`Arc<dyn Any + Send + Sync>`).
|
||||
- L'émission imposait de spécifier un topic string (`bus.emit("topic", event)`).
|
||||
- La réception nécessitait de spécifier à la fois le type et le topic string (`bus.on_async::<Event, _, _>("topic", ...)`), puis d'effectuer un déréférencement / downcast dynamique (`downcast_ref::<T>()` ou macro `match_event!`) sur chaque message reçu.
|
||||
|
||||
**`event_bus_typed` résout entièrement cette complexité :**
|
||||
- **Typage fort natif** : le routage est directement effectué par l'identifiant de type (`std::any::TypeId`), sans nom de topic requis.
|
||||
- **Zéro downcast / déréférencement à la réception** : le callback reçoit directement la structure d'événement typée.
|
||||
- **Syntaxe ergonomique** : support complet de la syntaxe turbofish demandée `bus.on_async::<MessageUpdatedEvent>(|event| async move { ... })` ainsi que de l'inférence automatique `bus.on_async(|event: MessageUpdatedEvent| async move { ... })`.
|
||||
- **Source unique de vérité** : plus besoin de `Scope` externe ; le contexte (ex: `channel_id`, `server_id`, `caller_id`) est directement transporté dans les champs de la structure typée.
|
||||
- **Targeted wake-up** : canaux Tokio `broadcast` isolés par type d'événement, garantissant des performances optimales sans réveil inutile de tâches.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Utilisation
|
||||
|
||||
### 1. Définir des événements
|
||||
|
||||
N'importe quelle structure Rust implémentant `Clone + Send + Sync + 'static` est automatiquement un `Event` valide (aucun macro derive supplémentaire nécessaire) :
|
||||
|
||||
```rust
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MessageCreatedEvent {
|
||||
pub server_id: Option<Uuid>,
|
||||
pub channel_id: Uuid,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MessageUpdatedEvent {
|
||||
pub id: u64,
|
||||
pub content: String,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Émission d'événements
|
||||
|
||||
```rust
|
||||
use event_bus_typed::EventBus;
|
||||
|
||||
let bus = EventBus::new();
|
||||
|
||||
// Émission typée directe
|
||||
bus.emit(MessageUpdatedEvent {
|
||||
id: 42,
|
||||
content: "Nouveau message".into(),
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Réception asynchrone (`on_async`)
|
||||
|
||||
Syntaxe turbofish exacte demandée :
|
||||
|
||||
```rust
|
||||
bus.on_async::<MessageUpdatedEvent>(|event| async move {
|
||||
// `event` est directement de type MessageUpdatedEvent
|
||||
println!("Message {} mis à jour : {}", event.id, event.content);
|
||||
});
|
||||
```
|
||||
|
||||
Ou avec inférence sur l'argument de fermeture :
|
||||
|
||||
```rust
|
||||
bus.on_async(|event: MessageUpdatedEvent| async move {
|
||||
println!("Contenu : {}", event.content);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Réception synchrone (`on`)
|
||||
|
||||
```rust
|
||||
bus.on::<MessageCreatedEvent>(|event| {
|
||||
println!("Nouveau message créé sur le salon : {:?}", event.channel_id);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Utilisation avec contexte injecté (`on_async_with`)
|
||||
|
||||
Pratique pour passer des services ou repositories sans clones manuels répétés :
|
||||
|
||||
```rust
|
||||
bus.on_async_with::<MessageCreatedEvent, _>(router, |router, event| async move {
|
||||
router.gateway.send(...);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. Écouteurs One-Shot (`wait_next` et `wait_for`)
|
||||
|
||||
Permet d'attendre un événement de manière linéaire avec une `Future` (sans boucle manuelle ni fuite de souscription) :
|
||||
|
||||
```rust
|
||||
// Attend le tout prochain événement de ce type
|
||||
let event = bus.wait_next::<MessageCreatedEvent>().await?;
|
||||
|
||||
// Ou attend un événement répondant à une condition précise
|
||||
let confirmed = bus.wait_for::<MessageSavedEvent>(|e| e.id == target_id).await?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. Flux direct / Récepteur sans callback (`subscribe`)
|
||||
|
||||
Si vous préférez consommer les événements dans votre propre boucle de streaming :
|
||||
|
||||
```rust
|
||||
let mut rx = bus.subscribe::<MessageUpdatedEvent>();
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Ok(event) = rx.recv().await {
|
||||
// `event` est directement MessageUpdatedEvent, aucun `match_event!` requis !
|
||||
println!("Reçu : {}", event.content);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests et Benchmarks
|
||||
|
||||
Exécuter les tests du crate :
|
||||
```bash
|
||||
cargo test --manifest-path event_bus_typed/Cargo.toml
|
||||
```
|
||||
|
||||
Exécuter les benchmarks Criterion :
|
||||
```bash
|
||||
cargo bench --manifest-path event_bus_typed/Cargo.toml
|
||||
```
|
||||
@@ -0,0 +1,488 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
use std::time::Instant;
|
||||
|
||||
use criterion::{Criterion, Throughput, criterion_group, criterion_main};
|
||||
use event_bus_typed::EventBus;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SmallEvent {
|
||||
value: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct StringEvent {
|
||||
id: u64,
|
||||
name: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct VecEvent {
|
||||
id: u64,
|
||||
payload: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ArcPayloadEvent {
|
||||
id: u64,
|
||||
payload: Arc<[u8]>,
|
||||
}
|
||||
|
||||
fn runtime() -> Runtime {
|
||||
Runtime::new().expect("failed to create tokio runtime")
|
||||
}
|
||||
|
||||
async fn wait_until_received(received: &AtomicU64, expected: u64) {
|
||||
while received.load(Ordering::Relaxed) < expected {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_emit_no_subscriber(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("event_bus_typed/no_subscriber");
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
let bus = EventBus::with_capacity(1024);
|
||||
|
||||
group.bench_function("u64", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(42_u64);
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("small_struct", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(SmallEvent { value: 42 });
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("string_struct", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(StringEvent {
|
||||
id: 42,
|
||||
name: "Alice".to_string(),
|
||||
message: "hello from benchmark".to_string(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("vec_payload_1kb", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(VecEvent {
|
||||
id: 42,
|
||||
payload: vec![7_u8; 1024],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let shared_payload: Arc<[u8]> = Arc::from(vec![7_u8; 1024].into_boxed_slice());
|
||||
|
||||
group.bench_function("arc_payload_1kb", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(ArcPayloadEvent {
|
||||
id: 42,
|
||||
payload: Arc::clone(&shared_payload),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_raw_subscriber(c: &mut Criterion) {
|
||||
let rt = runtime();
|
||||
|
||||
let mut group = c.benchmark_group("event_bus_typed/raw_subscriber");
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
group.bench_function("u64", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<u64>();
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
|
||||
let receiver = tokio::spawn(async move {
|
||||
while receiver_count.load(Ordering::Relaxed) < iters {
|
||||
if rx.recv().await.is_ok() {
|
||||
receiver_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(i);
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
receiver.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("small_struct", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<SmallEvent>();
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
|
||||
let receiver = tokio::spawn(async move {
|
||||
while receiver_count.load(Ordering::Relaxed) < iters {
|
||||
if rx.recv().await.is_ok() {
|
||||
receiver_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(SmallEvent { value: i });
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
receiver.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("string_struct", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<StringEvent>();
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
|
||||
let receiver = tokio::spawn(async move {
|
||||
while receiver_count.load(Ordering::Relaxed) < iters {
|
||||
if rx.recv().await.is_ok() {
|
||||
receiver_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(StringEvent {
|
||||
id: i,
|
||||
name: "Alice".to_string(),
|
||||
message: "hello from benchmark".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
receiver.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("vec_payload_1kb", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<VecEvent>();
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
|
||||
let receiver = tokio::spawn(async move {
|
||||
while receiver_count.load(Ordering::Relaxed) < iters {
|
||||
if rx.recv().await.is_ok() {
|
||||
receiver_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(VecEvent {
|
||||
id: i,
|
||||
payload: vec![7_u8; 1024],
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
receiver.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
let shared_payload: Arc<[u8]> = Arc::from(vec![7_u8; 1024].into_boxed_slice());
|
||||
|
||||
group.bench_function("arc_payload_1kb", |b| {
|
||||
let shared = Arc::clone(&shared_payload);
|
||||
b.to_async(&rt).iter_custom(|iters| {
|
||||
let payload = Arc::clone(&shared);
|
||||
async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<ArcPayloadEvent>();
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
|
||||
let receiver = tokio::spawn(async move {
|
||||
while receiver_count.load(Ordering::Relaxed) < iters {
|
||||
if rx.recv().await.is_ok() {
|
||||
receiver_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(ArcPayloadEvent {
|
||||
id: i,
|
||||
payload: Arc::clone(&payload),
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
receiver.abort();
|
||||
elapsed
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_typed_callback(c: &mut Criterion) {
|
||||
let rt = runtime();
|
||||
|
||||
let mut group = c.benchmark_group("event_bus_typed/typed_callback");
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
group.bench_function("u64", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<u64>(move |event| {
|
||||
let _ = event;
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(i);
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
subscription.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("small_struct", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<SmallEvent>(move |event| {
|
||||
let _ = event.value;
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(SmallEvent { value: i });
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
subscription.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("string_struct", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<StringEvent>(move |event| {
|
||||
let _ = event.id;
|
||||
let _ = event.name.len();
|
||||
let _ = event.message.len();
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(StringEvent {
|
||||
id: i,
|
||||
name: "Alice".to_string(),
|
||||
message: "hello from benchmark".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
subscription.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("vec_payload_1kb", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<VecEvent>(move |event| {
|
||||
let _ = event.id;
|
||||
let _ = event.payload.len();
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(VecEvent {
|
||||
id: i,
|
||||
payload: vec![7_u8; 1024],
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
subscription.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
let shared_payload: Arc<[u8]> = Arc::from(vec![7_u8; 1024].into_boxed_slice());
|
||||
|
||||
group.bench_function("arc_payload_1kb", |b| {
|
||||
let shared = Arc::clone(&shared_payload);
|
||||
b.to_async(&rt).iter_custom(|iters| {
|
||||
let payload = Arc::clone(&shared);
|
||||
async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<ArcPayloadEvent>(move |event| {
|
||||
let _ = event.id;
|
||||
let _ = event.payload.len();
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(ArcPayloadEvent {
|
||||
id: i,
|
||||
payload: Arc::clone(&payload),
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
subscription.abort();
|
||||
elapsed
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_multiple_subscribers(c: &mut Criterion) {
|
||||
let rt = runtime();
|
||||
|
||||
let mut group = c.benchmark_group("event_bus_typed/multiple_subscribers");
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
for subscriber_count in [1_u64, 2, 4, 8, 16, 32] {
|
||||
group.bench_function(format!("{subscriber_count}_subscribers"), |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let expected = iters * subscriber_count;
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let mut subscriptions = Vec::with_capacity(subscriber_count as usize);
|
||||
|
||||
for _ in 0..subscriber_count {
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<SmallEvent>(move |event| {
|
||||
let _ = event.value;
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
subscriptions.push(subscription);
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(SmallEvent { value: i });
|
||||
}
|
||||
|
||||
wait_until_received(&received, expected).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
for subscription in subscriptions {
|
||||
subscription.abort();
|
||||
}
|
||||
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_emit_no_subscriber,
|
||||
bench_raw_subscriber,
|
||||
bench_typed_callback,
|
||||
bench_multiple_subscribers,
|
||||
);
|
||||
|
||||
criterion_main!(benches);
|
||||
@@ -0,0 +1,433 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
use crate::handler::{AsyncHandler, AsyncHandlerWith};
|
||||
|
||||
/// Default buffer capacity for each broadcast channel.
|
||||
pub const DEFAULT_CAPACITY: usize = 1024;
|
||||
|
||||
/// Marker trait for events dispatched through [`EventBus`].
|
||||
///
|
||||
/// Any type implementing `Clone + Send + Sync + 'static` automatically
|
||||
/// implements `Event`.
|
||||
pub trait Event: Clone + Send + Sync + 'static {}
|
||||
|
||||
impl<T: Clone + Send + Sync + 'static> Event for T {}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct EventBusInner {
|
||||
channels: RwLock<HashMap<TypeId, Box<dyn Any + Send + Sync>>>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
/// A central, strongly-typed broadcast event bus.
|
||||
///
|
||||
/// Unlike string/topic-based event buses, [`EventBus`] dispatches events directly
|
||||
/// based on the concrete Rust type of the event (using [`TypeId`]).
|
||||
///
|
||||
/// Under the hood, each event type is backed by an independent [`tokio::sync::broadcast`]
|
||||
/// ring buffer. Dispatching an event via [`emit`](Self::emit) is $O(1)$ and non-blocking.
|
||||
///
|
||||
/// # Sync callback example
|
||||
/// ```rust,no_run
|
||||
/// use event_bus_typed::EventBus;
|
||||
///
|
||||
/// #[derive(Clone, Debug)]
|
||||
/// struct MessageCreatedEvent {
|
||||
/// content: String,
|
||||
/// }
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
///
|
||||
/// bus.on::<MessageCreatedEvent>(|event| {
|
||||
/// println!("Created message: {:?}", event);
|
||||
/// });
|
||||
///
|
||||
/// bus.emit(MessageCreatedEvent {
|
||||
/// content: "Hello!".into(),
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Async callback example
|
||||
/// ```rust,no_run
|
||||
/// use event_bus_typed::EventBus;
|
||||
///
|
||||
/// #[derive(Clone, Debug)]
|
||||
/// struct MessageUpdatedEvent {
|
||||
/// id: u64,
|
||||
/// content: String,
|
||||
/// }
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
///
|
||||
/// bus.on_async::<MessageUpdatedEvent>(|event| async move {
|
||||
/// println!("(async) Updated message: {:?}", event);
|
||||
/// });
|
||||
///
|
||||
/// bus.emit(MessageUpdatedEvent {
|
||||
/// id: 42,
|
||||
/// content: "World!".into(),
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventBus {
|
||||
inner: Arc<EventBusInner>,
|
||||
}
|
||||
|
||||
impl Default for EventBus {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventBus {
|
||||
/// Creates a new `EventBus` with default capacity (1024 messages per channel).
|
||||
pub fn new() -> Self {
|
||||
Self::with_capacity(DEFAULT_CAPACITY)
|
||||
}
|
||||
|
||||
/// Creates a new `EventBus` with custom buffer capacity per channel.
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
debug!(capacity, "EventBus created");
|
||||
Self {
|
||||
inner: Arc::new(EventBusInner {
|
||||
channels: RwLock::new(HashMap::new()),
|
||||
capacity,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Internal Channel Management
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn get_or_create_sender<E: Event>(&self) -> broadcast::Sender<E> {
|
||||
let type_id = TypeId::of::<E>();
|
||||
|
||||
if let Some(tx) = self
|
||||
.inner
|
||||
.channels
|
||||
.read()
|
||||
.get(&type_id)
|
||||
.and_then(|entry| entry.downcast_ref::<broadcast::Sender<E>>())
|
||||
{
|
||||
return tx.clone();
|
||||
}
|
||||
|
||||
let mut channels = self.inner.channels.write();
|
||||
if let Some(tx) = channels
|
||||
.get(&type_id)
|
||||
.and_then(|entry| entry.downcast_ref::<broadcast::Sender<E>>())
|
||||
{
|
||||
return tx.clone();
|
||||
}
|
||||
|
||||
let (tx, _) = broadcast::channel::<E>(self.inner.capacity);
|
||||
debug!(
|
||||
event_type = std::any::type_name::<E>(),
|
||||
capacity = self.inner.capacity,
|
||||
"New broadcast channel created"
|
||||
);
|
||||
channels.insert(type_id, Box::new(tx.clone()));
|
||||
tx
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Emission
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Emits a strongly-typed event to all subscribers of `E` without blocking ($O(1)$).
|
||||
///
|
||||
/// If no subscribers exist for this event type, the event is dropped immediately
|
||||
/// without cloning or allocating.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use event_bus_typed::EventBus;
|
||||
/// # #[derive(Clone)] struct UserConnected { id: u64 }
|
||||
/// # let bus = EventBus::new();
|
||||
/// bus.emit(UserConnected { id: 1 });
|
||||
/// ```
|
||||
pub fn emit<E: Event>(&self, event: E) {
|
||||
trace!(
|
||||
event_type = std::any::type_name::<E>(),
|
||||
"Emitting event"
|
||||
);
|
||||
|
||||
let type_id = TypeId::of::<E>();
|
||||
|
||||
if let Some(tx) = self
|
||||
.inner
|
||||
.channels
|
||||
.read()
|
||||
.get(&type_id)
|
||||
.and_then(|entry| entry.downcast_ref::<broadcast::Sender<E>>())
|
||||
.filter(|tx| tx.receiver_count() > 0)
|
||||
{
|
||||
let _ = tx.send(event);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Subscriptions — Callbacks
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Registers a synchronous callback for events of type `E`.
|
||||
///
|
||||
/// The handler runs in a dedicated background Tokio task.
|
||||
/// Returns a [`JoinHandle`] allowing to cancel the subscription via [`.abort()`](JoinHandle::abort).
|
||||
pub fn on<E: Event>(&self, handler: impl Fn(E) + Send + Sync + 'static) -> JoinHandle<()> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
let type_name = std::any::type_name::<E>();
|
||||
|
||||
debug!(event_type = type_name, "Sync subscriber registered");
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
trace!(event_type = type_name, "Sync handler invoked");
|
||||
handler(event);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(skipped)) => {
|
||||
warn!(
|
||||
event_type = type_name,
|
||||
skipped,
|
||||
"Subscriber lagged behind and skipped messages"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
debug!(event_type = type_name, "Channel closed, subscriber exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers an asynchronous callback for events of type `E`.
|
||||
///
|
||||
/// The handler runs in a dedicated background Tokio task.
|
||||
/// Returns a [`JoinHandle`] allowing to cancel the subscription via [`.abort()`](JoinHandle::abort).
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use event_bus_typed::EventBus;
|
||||
/// # #[derive(Clone)] struct MessageUpdatedEvent { id: u64 }
|
||||
/// # let bus = EventBus::new();
|
||||
/// bus.on_async::<MessageUpdatedEvent>(|event| async move {
|
||||
/// println!("Updated: {:?}", event.id);
|
||||
/// });
|
||||
/// ```
|
||||
pub fn on_async<E: Event>(&self, handler: impl AsyncHandler<E>) -> JoinHandle<()> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
let type_name = std::any::type_name::<E>();
|
||||
|
||||
debug!(event_type = type_name, "Async subscriber registered");
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
trace!(event_type = type_name, "Async handler invoked");
|
||||
handler(event).await;
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(skipped)) => {
|
||||
warn!(
|
||||
event_type = type_name,
|
||||
skipped,
|
||||
"Subscriber lagged behind and skipped messages"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
debug!(event_type = type_name, "Channel closed, subscriber exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers an asynchronous callback for events of type `E` with an injected context `C`.
|
||||
pub fn on_async_with<E: Event, C: Clone + Send + Sync + 'static>(
|
||||
&self,
|
||||
context: C,
|
||||
handler: impl AsyncHandlerWith<E, C>,
|
||||
) -> JoinHandle<()> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
let type_name = std::any::type_name::<E>();
|
||||
|
||||
debug!(event_type = type_name, "Async subscriber with context registered");
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
trace!(event_type = type_name, "Async handler with context invoked");
|
||||
handler(context.clone(), event).await;
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(skipped)) => {
|
||||
warn!(
|
||||
event_type = type_name,
|
||||
skipped,
|
||||
"Subscriber lagged behind and skipped messages"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
debug!(event_type = type_name, "Channel closed, subscriber exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Low-Level Subscription (Direct Stream / Receiver)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Returns a direct [`broadcast::Receiver<E>`] for events of type `E`.
|
||||
///
|
||||
/// Allows writing custom event processing loops without callback wrappers.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// use event_bus_typed::EventBus;
|
||||
///
|
||||
/// #[derive(Clone)]
|
||||
/// struct MyEvent;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
/// let mut rx = bus.subscribe::<MyEvent>();
|
||||
/// bus.emit(MyEvent);
|
||||
/// if let Ok(event) = rx.recv().await {
|
||||
/// // direct typed `event`
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn subscribe<E: Event>(&self) -> broadcast::Receiver<E> {
|
||||
self.get_or_create_sender::<E>().subscribe()
|
||||
}
|
||||
|
||||
/// Waits for the next event of type `E` to be emitted.
|
||||
///
|
||||
/// Creates a temporary one-shot subscription and resolves as soon as an event of type `E`
|
||||
/// is emitted. The subscription is automatically dropped after receiving the event.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use event_bus_typed::EventBus;
|
||||
/// # #[derive(Clone)] struct MyEvent { id: u64 }
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
/// let event = bus.wait_next::<MyEvent>().await.unwrap();
|
||||
/// println!("Received next event: {}", event.id);
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn wait_next<E: Event>(&self) -> Result<E, broadcast::error::RecvError> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
rx.recv().await
|
||||
}
|
||||
|
||||
/// Waits for an event of type `E` satisfying the given predicate to be emitted.
|
||||
///
|
||||
/// Creates a temporary subscription, receives events of type `E`, and resolves
|
||||
/// when the predicate returns `true`. The subscription is automatically dropped afterwards.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use event_bus_typed::EventBus;
|
||||
/// # #[derive(Clone)] struct MessageSaved { id: u64 }
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
/// let target_id = 42;
|
||||
/// let event = bus.wait_for::<MessageSaved>(|e| e.id == target_id).await.unwrap();
|
||||
/// println!("Saved message confirmed: {}", event.id);
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn wait_for<E: Event>(
|
||||
&self,
|
||||
mut predicate: impl FnMut(&E) -> bool,
|
||||
) -> Result<E, broadcast::error::RecvError> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
if predicate(&event) {
|
||||
return Ok(event);
|
||||
}
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(skipped)) => {
|
||||
warn!(
|
||||
event_type = std::any::type_name::<E>(),
|
||||
skipped,
|
||||
"wait_for subscriber lagged behind and skipped messages"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
return Err(broadcast::error::RecvError::Closed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Metrics & Utilities
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Returns the total number of active subscribers for event type `E`.
|
||||
pub fn subscriber_count<E: Event>(&self) -> usize {
|
||||
let type_id = TypeId::of::<E>();
|
||||
|
||||
self.inner
|
||||
.channels
|
||||
.read()
|
||||
.get(&type_id)
|
||||
.and_then(|e| e.downcast_ref::<broadcast::Sender<E>>())
|
||||
.map(|tx| tx.receiver_count())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Returns `true` if there are any active subscribers for event type `E`.
|
||||
pub fn has_subscribers<E: Event>(&self) -> bool {
|
||||
self.subscriber_count::<E>() > 0
|
||||
}
|
||||
|
||||
/// Returns the total number of broadcast channels currently instantiated in the bus.
|
||||
pub fn channel_count(&self) -> usize {
|
||||
self.inner.channels.read().len()
|
||||
}
|
||||
|
||||
/// Returns `true` if no channels are currently registered in the bus.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.channels.read().is_empty()
|
||||
}
|
||||
|
||||
/// Removes and drops all channels in the event bus.
|
||||
pub fn clear(&self) {
|
||||
self.inner.channels.write().clear();
|
||||
}
|
||||
|
||||
/// Removes and drops the channel associated with event type `E`.
|
||||
pub fn clear_type<E: Event>(&self) {
|
||||
let type_id = TypeId::of::<E>();
|
||||
self.inner.channels.write().remove(&type_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use std::future::Future;
|
||||
|
||||
/// Handler trait for asynchronous event callbacks.
|
||||
///
|
||||
/// This trait is automatically implemented for any closure matching
|
||||
/// `Fn(E) -> Future<Output = ()> + Send + Sync + 'static`.
|
||||
///
|
||||
/// By using `Fn(E) -> <Self as AsyncHandler<E>>::Fut` as a supertrait,
|
||||
/// Rust propagates the event type `E` directly into the closure's parameter,
|
||||
/// enabling seamless turbofish syntax with clean field access:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use event_bus_typed::EventBus;
|
||||
/// # #[derive(Clone)] struct MessageUpdatedEvent { id: u64 }
|
||||
/// # let bus = EventBus::new();
|
||||
/// bus.on_async::<MessageUpdatedEvent>(|event| async move {
|
||||
/// println!("Updated: {:?}", event.id);
|
||||
/// });
|
||||
/// ```
|
||||
pub trait AsyncHandler<E>: Fn(E) -> <Self as AsyncHandler<E>>::Fut + Send + Sync + 'static {
|
||||
type Fut: Future<Output = ()> + Send + 'static;
|
||||
}
|
||||
|
||||
impl<E, F, Fut> AsyncHandler<E> for F
|
||||
where
|
||||
F: Fn(E) -> Fut + Send + Sync + 'static,
|
||||
Fut: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
type Fut = Fut;
|
||||
}
|
||||
|
||||
/// Handler trait for asynchronous event callbacks with an injected context.
|
||||
///
|
||||
/// Automatically implemented for closures matching
|
||||
/// `Fn(C, E) -> Future<Output = ()> + Send + Sync + 'static`.
|
||||
pub trait AsyncHandlerWith<E, C>: Fn(C, E) -> <Self as AsyncHandlerWith<E, C>>::Fut + Send + Sync + 'static {
|
||||
type Fut: Future<Output = ()> + Send + 'static;
|
||||
}
|
||||
|
||||
impl<E, C, F, Fut> AsyncHandlerWith<E, C> for F
|
||||
where
|
||||
C: Clone + Send + Sync + 'static,
|
||||
F: Fn(C, E) -> Fut + Send + Sync + 'static,
|
||||
Fut: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
type Fut = Fut;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//! # event_bus_typed
|
||||
//!
|
||||
//! A strongly-typed, high-performance in-memory event bus for Tokio.
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! Unlike string/topic-based event buses, `event_bus_typed` routes events using
|
||||
//! their concrete Rust types ([`std::any::TypeId`]).
|
||||
//!
|
||||
//! - **Strong typing**: No string keys required for event types, no manual `match_event!`
|
||||
//! macros, and no runtime downcasting (`downcast_ref`) inside the subscriber loops.
|
||||
//! - **Ergonomic async subscribers**: Handlers can be registered with clean turbofish syntax:
|
||||
//! `bus.on_async::<MessageUpdatedEvent>(|event| async move { ... })`.
|
||||
//! - **Targeted wake-up**: Tokio broadcast channels are isolated per event type.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use event_bus_typed::EventBus;
|
||||
//!
|
||||
//! #[derive(Clone, Debug, PartialEq)]
|
||||
//! struct MessageCreatedEvent {
|
||||
//! content: String,
|
||||
//! }
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let bus = EventBus::new();
|
||||
//!
|
||||
//! // Async subscriber
|
||||
//! bus.on_async::<MessageCreatedEvent>(|event| async move {
|
||||
//! println!("Received message: {}", event.content);
|
||||
//! });
|
||||
//!
|
||||
//! // Emit event
|
||||
//! bus.emit(MessageCreatedEvent {
|
||||
//! content: "Hello from typed event bus!".into(),
|
||||
//! });
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
mod bus;
|
||||
mod handler;
|
||||
|
||||
pub use bus::{DEFAULT_CAPACITY, Event, EventBus};
|
||||
pub use handler::{AsyncHandler, AsyncHandlerWith};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -0,0 +1,364 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::EventBus;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct MessageCreatedEvent {
|
||||
channel_id: Uuid,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct MessageUpdatedEvent {
|
||||
id: u64,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct MessageDeletedEvent {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct UdpMetricEvent {
|
||||
value: f32,
|
||||
}
|
||||
|
||||
// ── Sync Callbacks ──────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_callback_sync() {
|
||||
let bus = EventBus::new();
|
||||
let received = Arc::new(AtomicBool::new(false));
|
||||
let flag = Arc::clone(&received);
|
||||
|
||||
bus.on::<MessageCreatedEvent>(move |event| {
|
||||
if event.content == "Hello" {
|
||||
flag.store(true, Ordering::SeqCst);
|
||||
}
|
||||
});
|
||||
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Hello".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert!(received.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
// ── Async Callbacks (Exact User Requirement) ────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_async_callback_turbofish() {
|
||||
let bus = EventBus::new();
|
||||
let received_content = Arc::new(tokio::sync::Mutex::new(String::new()));
|
||||
let rc = Arc::clone(&received_content);
|
||||
|
||||
// Exact syntax specified by the user:
|
||||
// event_bus.on_async::<MessageUpdatedEvent>(|event| async move { ... });
|
||||
bus.on_async::<MessageUpdatedEvent>(move |event| {
|
||||
let rc = Arc::clone(&rc);
|
||||
async move {
|
||||
let mut lock = rc.lock().await;
|
||||
*lock = event.content;
|
||||
}
|
||||
});
|
||||
|
||||
// Exact syntax specified by the user:
|
||||
// event_bus.emit(MessageUpdatedEvent { ... });
|
||||
bus.emit(MessageUpdatedEvent {
|
||||
id: 42,
|
||||
content: "Updated message content".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
|
||||
let result = received_content.lock().await.clone();
|
||||
assert_eq!(result, "Updated message content");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_async_callback_type_inferred() {
|
||||
let bus = EventBus::new();
|
||||
let flag = Arc::new(AtomicBool::new(false));
|
||||
let f = Arc::clone(&flag);
|
||||
|
||||
// Also supports inferring the event type from closure parameter
|
||||
bus.on_async(move |event: MessageUpdatedEvent| {
|
||||
let f = Arc::clone(&f);
|
||||
async move {
|
||||
if event.id == 99 {
|
||||
f.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
bus.emit(MessageUpdatedEvent {
|
||||
id: 99,
|
||||
content: "Inferred".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert!(flag.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
// ── Targeted Wake-Up & Type Isolation ───────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_targeted_wakeup() {
|
||||
let bus = EventBus::new();
|
||||
let metric_called = Arc::new(AtomicBool::new(false));
|
||||
let flag = Arc::clone(&metric_called);
|
||||
|
||||
bus.on::<UdpMetricEvent>(move |_| {
|
||||
flag.store(true, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
// Emitting MessageCreatedEvent must never wake up UdpMetricEvent subscribers
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Ignore me".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert!(!metric_called.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_multiple_subscribers_same_type() {
|
||||
let bus = EventBus::new();
|
||||
let count = Arc::new(AtomicU32::new(0));
|
||||
|
||||
for _ in 0..3 {
|
||||
let c = Arc::clone(&count);
|
||||
bus.on::<MessageCreatedEvent>(move |_| {
|
||||
c.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
}
|
||||
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Broadcast".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert_eq!(count.load(Ordering::SeqCst), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_multiple_different_types_same_bus() {
|
||||
let bus = EventBus::new();
|
||||
let msg_ok = Arc::new(AtomicBool::new(false));
|
||||
let metric_ok = Arc::new(AtomicBool::new(false));
|
||||
let m_flag = Arc::clone(&msg_ok);
|
||||
let u_flag = Arc::clone(&metric_ok);
|
||||
|
||||
bus.on::<MessageCreatedEvent>(move |event| {
|
||||
if event.content == "Test" {
|
||||
m_flag.store(true, Ordering::SeqCst);
|
||||
}
|
||||
});
|
||||
|
||||
bus.on::<UdpMetricEvent>(move |metric| {
|
||||
if (metric.value - 42.5).abs() < 0.001 {
|
||||
u_flag.store(true, Ordering::SeqCst);
|
||||
}
|
||||
});
|
||||
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Test".into(),
|
||||
});
|
||||
bus.emit(UdpMetricEvent { value: 42.5 });
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert!(msg_ok.load(Ordering::SeqCst));
|
||||
assert!(metric_ok.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
// ── Direct Stream / Receiver (No match_event! needed) ───────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_subscribe_direct_typed_receiver() {
|
||||
let bus = EventBus::new();
|
||||
let mut rx = bus.subscribe::<MessageUpdatedEvent>();
|
||||
|
||||
bus.emit(MessageUpdatedEvent {
|
||||
id: 123,
|
||||
content: "Direct typed".into(),
|
||||
});
|
||||
|
||||
let event = rx.recv().await.expect("failed to receive event");
|
||||
// event is directly of type MessageUpdatedEvent, no downcast needed!
|
||||
assert_eq!(event.id, 123);
|
||||
assert_eq!(event.content, "Direct typed");
|
||||
}
|
||||
|
||||
// ── In-Handler Filtering (Direct Field Access) ──────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_filter_by_field_in_subscriber() {
|
||||
let bus = EventBus::new();
|
||||
let channel_a = Uuid::new_v4();
|
||||
let channel_b = Uuid::new_v4();
|
||||
|
||||
let count_a = Arc::new(AtomicU32::new(0));
|
||||
let count_b = Arc::new(AtomicU32::new(0));
|
||||
let count_global = Arc::new(AtomicU32::new(0));
|
||||
|
||||
let ca = Arc::clone(&count_a);
|
||||
bus.on_async::<MessageCreatedEvent>(move |event| {
|
||||
let ca = Arc::clone(&ca);
|
||||
async move {
|
||||
if event.channel_id == channel_a {
|
||||
ca.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let cb = Arc::clone(&count_b);
|
||||
bus.on_async::<MessageCreatedEvent>(move |event| {
|
||||
let cb = Arc::clone(&cb);
|
||||
async move {
|
||||
if event.channel_id == channel_b {
|
||||
cb.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let cg = Arc::clone(&count_global);
|
||||
bus.on::<MessageCreatedEvent>(move |_| {
|
||||
cg.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
// Emit event with channel_a
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: channel_a,
|
||||
content: "For A".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
|
||||
// Both channel_a handler and global handler processed it, but not channel_b
|
||||
assert_eq!(count_a.load(Ordering::SeqCst), 1);
|
||||
assert_eq!(count_b.load(Ordering::SeqCst), 0);
|
||||
assert_eq!(count_global.load(Ordering::SeqCst), 1);
|
||||
}
|
||||
|
||||
// ── Async with Context ──────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_async_with_context() {
|
||||
let bus = EventBus::new();
|
||||
let prefix = Arc::new("Prefix: ".to_string());
|
||||
let result = Arc::new(tokio::sync::Mutex::new(String::new()));
|
||||
let r = Arc::clone(&result);
|
||||
|
||||
bus.on_async_with::<MessageCreatedEvent, _>(prefix, move |ctx, event| {
|
||||
let r = Arc::clone(&r);
|
||||
async move {
|
||||
let mut lock = r.lock().await;
|
||||
*lock = format!("{}{}", ctx, event.content);
|
||||
}
|
||||
});
|
||||
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Hello Context".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
let final_str = result.lock().await.clone();
|
||||
assert_eq!(final_str, "Prefix: Hello Context");
|
||||
}
|
||||
|
||||
// ── Metrics, Utilities & Cleanup ────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_subscriber_count_and_clear() {
|
||||
let bus = EventBus::new();
|
||||
assert_eq!(bus.subscriber_count::<MessageCreatedEvent>(), 0);
|
||||
assert!(!bus.has_subscribers::<MessageCreatedEvent>());
|
||||
|
||||
let _sub = bus.subscribe::<MessageCreatedEvent>();
|
||||
assert_eq!(bus.subscriber_count::<MessageCreatedEvent>(), 1);
|
||||
assert!(bus.has_subscribers::<MessageCreatedEvent>());
|
||||
assert_eq!(bus.channel_count(), 1);
|
||||
|
||||
bus.clear_type::<MessageCreatedEvent>();
|
||||
assert_eq!(bus.subscriber_count::<MessageCreatedEvent>(), 0);
|
||||
assert_eq!(bus.channel_count(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_subscription_abort() {
|
||||
let bus = EventBus::new();
|
||||
let count = Arc::new(AtomicU32::new(0));
|
||||
let c = Arc::clone(&count);
|
||||
|
||||
let handle = bus.on::<MessageDeletedEvent>(move |_| {
|
||||
c.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
bus.emit(MessageDeletedEvent { id: 1 });
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert_eq!(count.load(Ordering::SeqCst), 1);
|
||||
|
||||
// Cancel the subscription
|
||||
handle.abort();
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
|
||||
bus.emit(MessageDeletedEvent { id: 2 });
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
// Count should not increase after abort
|
||||
assert_eq!(count.load(Ordering::SeqCst), 1);
|
||||
}
|
||||
|
||||
// ── One-Shot Listeners (wait_next & wait_for) ────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_wait_next() {
|
||||
let bus = EventBus::new();
|
||||
let b = bus.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
b.emit(MessageUpdatedEvent {
|
||||
id: 777,
|
||||
content: "Next event".into(),
|
||||
});
|
||||
});
|
||||
|
||||
let event = bus.wait_next::<MessageUpdatedEvent>().await.unwrap();
|
||||
assert_eq!(event.id, 777);
|
||||
assert_eq!(event.content, "Next event");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_wait_for() {
|
||||
let bus = EventBus::new();
|
||||
let b = bus.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
b.emit(MessageUpdatedEvent {
|
||||
id: 1,
|
||||
content: "Ignore".into(),
|
||||
});
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
b.emit(MessageUpdatedEvent {
|
||||
id: 2,
|
||||
content: "Target".into(),
|
||||
});
|
||||
});
|
||||
|
||||
let event = bus
|
||||
.wait_for::<MessageUpdatedEvent>(|e| e.id == 2)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(event.id, 2);
|
||||
assert_eq!(event.content, "Target");
|
||||
}
|
||||
Reference in New Issue
Block a user