├── .github ├── FUNDING.yml └── workflows │ └── build.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── DEVELOPMENT.md ├── LICENSE ├── README.md ├── docker ├── Dockerfile ├── app.entrypoint.sh └── lunatic ├── lucidity-core ├── Cargo.toml └── src │ └── lib.rs ├── lucidity-macros ├── Cargo.toml └── src │ └── lib.rs ├── lucidity ├── Cargo.toml └── src │ ├── fly.rs │ └── lib.rs ├── rust-toolchain.toml └── rustfmt.toml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [twitchax] 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: Build and Test 4 | 5 | jobs: 6 | 7 | build: 8 | name: Build 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions-rs/toolchain@v1 13 | with: 14 | toolchain: nightly 15 | override: true 16 | - uses: Swatinem/rust-cache@v2 17 | - uses: actions-rs/cargo@v1 18 | with: 19 | command: build 20 | 21 | test: 22 | name: Test 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: nightly 29 | override: true 30 | - uses: Swatinem/rust-cache@v2 31 | - uses: actions-rs/cargo@v1 32 | with: 33 | command: test 34 | args: -p lucidity-macros -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode/ -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "alloc-no-stdlib" 13 | version = "2.0.4" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 16 | 17 | [[package]] 18 | name = "alloc-stdlib" 19 | version = "0.2.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 22 | dependencies = [ 23 | "alloc-no-stdlib", 24 | ] 25 | 26 | [[package]] 27 | name = "android-tzdata" 28 | version = "0.1.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 31 | 32 | [[package]] 33 | name = "android_system_properties" 34 | version = "0.1.5" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 37 | dependencies = [ 38 | "libc", 39 | ] 40 | 41 | [[package]] 42 | name = "autocfg" 43 | version = "1.1.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 46 | 47 | [[package]] 48 | name = "base-x" 49 | version = "0.2.11" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 52 | 53 | [[package]] 54 | name = "base64" 55 | version = "0.13.1" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 58 | 59 | [[package]] 60 | name = "bincode" 61 | version = "1.3.3" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 64 | dependencies = [ 65 | "serde", 66 | ] 67 | 68 | [[package]] 69 | name = "brotli" 70 | version = "3.4.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" 73 | dependencies = [ 74 | "alloc-no-stdlib", 75 | "alloc-stdlib", 76 | "brotli-decompressor", 77 | ] 78 | 79 | [[package]] 80 | name = "brotli-decompressor" 81 | version = "2.5.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" 84 | dependencies = [ 85 | "alloc-no-stdlib", 86 | "alloc-stdlib", 87 | ] 88 | 89 | [[package]] 90 | name = "bumpalo" 91 | version = "3.14.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" 94 | 95 | [[package]] 96 | name = "bytes" 97 | version = "1.5.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 100 | 101 | [[package]] 102 | name = "cc" 103 | version = "1.0.83" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 106 | dependencies = [ 107 | "libc", 108 | ] 109 | 110 | [[package]] 111 | name = "cfg-if" 112 | version = "1.0.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 115 | 116 | [[package]] 117 | name = "chrono" 118 | version = "0.4.31" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" 121 | dependencies = [ 122 | "android-tzdata", 123 | "iana-time-zone", 124 | "js-sys", 125 | "num-traits", 126 | "wasm-bindgen", 127 | "windows-targets 0.48.5", 128 | ] 129 | 130 | [[package]] 131 | name = "const_fn" 132 | version = "0.4.9" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" 135 | 136 | [[package]] 137 | name = "convert_case" 138 | version = "0.6.0" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" 141 | dependencies = [ 142 | "unicode-segmentation", 143 | ] 144 | 145 | [[package]] 146 | name = "cookie" 147 | version = "0.15.2" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "fc6e25dfc584d06a3dbf775d207ff00d7de98d824c952dd2233dfbb261889a42" 150 | dependencies = [ 151 | "percent-encoding", 152 | "time", 153 | "version_check", 154 | ] 155 | 156 | [[package]] 157 | name = "cookie_store" 158 | version = "0.15.1" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "b3f7034c0932dc36f5bd8ec37368d971346809435824f277cb3b8299fc56167c" 161 | dependencies = [ 162 | "cookie", 163 | "idna 0.2.3", 164 | "log", 165 | "publicsuffix", 166 | "serde", 167 | "serde_json", 168 | "time", 169 | "url", 170 | ] 171 | 172 | [[package]] 173 | name = "core-foundation-sys" 174 | version = "0.8.6" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 177 | 178 | [[package]] 179 | name = "crc32fast" 180 | version = "1.3.2" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 183 | dependencies = [ 184 | "cfg-if", 185 | ] 186 | 187 | [[package]] 188 | name = "darling" 189 | version = "0.14.4" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" 192 | dependencies = [ 193 | "darling_core", 194 | "darling_macro", 195 | ] 196 | 197 | [[package]] 198 | name = "darling_core" 199 | version = "0.14.4" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" 202 | dependencies = [ 203 | "fnv", 204 | "ident_case", 205 | "proc-macro2", 206 | "quote", 207 | "strsim", 208 | "syn 1.0.109", 209 | ] 210 | 211 | [[package]] 212 | name = "darling_macro" 213 | version = "0.14.4" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" 216 | dependencies = [ 217 | "darling_core", 218 | "quote", 219 | "syn 1.0.109", 220 | ] 221 | 222 | [[package]] 223 | name = "diff" 224 | version = "0.1.13" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 227 | 228 | [[package]] 229 | name = "discard" 230 | version = "1.0.4" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" 233 | 234 | [[package]] 235 | name = "encoding_rs" 236 | version = "0.8.33" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" 239 | dependencies = [ 240 | "cfg-if", 241 | ] 242 | 243 | [[package]] 244 | name = "flate2" 245 | version = "1.0.28" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" 248 | dependencies = [ 249 | "crc32fast", 250 | "miniz_oxide", 251 | ] 252 | 253 | [[package]] 254 | name = "fnv" 255 | version = "1.0.7" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 258 | 259 | [[package]] 260 | name = "form_urlencoded" 261 | version = "1.2.1" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 264 | dependencies = [ 265 | "percent-encoding", 266 | ] 267 | 268 | [[package]] 269 | name = "getrandom" 270 | version = "0.2.11" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" 273 | dependencies = [ 274 | "cfg-if", 275 | "libc", 276 | "wasi", 277 | ] 278 | 279 | [[package]] 280 | name = "http" 281 | version = "0.2.11" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" 284 | dependencies = [ 285 | "bytes", 286 | "fnv", 287 | "itoa", 288 | ] 289 | 290 | [[package]] 291 | name = "http-body" 292 | version = "0.4.6" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 295 | dependencies = [ 296 | "bytes", 297 | "http", 298 | "pin-project-lite", 299 | ] 300 | 301 | [[package]] 302 | name = "httparse" 303 | version = "1.8.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 306 | 307 | [[package]] 308 | name = "iana-time-zone" 309 | version = "0.1.59" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" 312 | dependencies = [ 313 | "android_system_properties", 314 | "core-foundation-sys", 315 | "iana-time-zone-haiku", 316 | "js-sys", 317 | "wasm-bindgen", 318 | "windows-core", 319 | ] 320 | 321 | [[package]] 322 | name = "iana-time-zone-haiku" 323 | version = "0.1.2" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 326 | dependencies = [ 327 | "cc", 328 | ] 329 | 330 | [[package]] 331 | name = "ident_case" 332 | version = "1.0.1" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 335 | 336 | [[package]] 337 | name = "idna" 338 | version = "0.2.3" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 341 | dependencies = [ 342 | "matches", 343 | "unicode-bidi", 344 | "unicode-normalization", 345 | ] 346 | 347 | [[package]] 348 | name = "idna" 349 | version = "0.3.0" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 352 | dependencies = [ 353 | "unicode-bidi", 354 | "unicode-normalization", 355 | ] 356 | 357 | [[package]] 358 | name = "idna" 359 | version = "0.5.0" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 362 | dependencies = [ 363 | "unicode-bidi", 364 | "unicode-normalization", 365 | ] 366 | 367 | [[package]] 368 | name = "ipnet" 369 | version = "2.9.0" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 372 | 373 | [[package]] 374 | name = "itoa" 375 | version = "1.0.10" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 378 | 379 | [[package]] 380 | name = "js-sys" 381 | version = "0.3.66" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" 384 | dependencies = [ 385 | "wasm-bindgen", 386 | ] 387 | 388 | [[package]] 389 | name = "libc" 390 | version = "0.2.151" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" 393 | 394 | [[package]] 395 | name = "log" 396 | version = "0.4.20" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 399 | 400 | [[package]] 401 | name = "lucidity" 402 | version = "0.1.0" 403 | dependencies = [ 404 | "lucidity-core", 405 | "lucidity-macros", 406 | "nightfly", 407 | "serde_json", 408 | ] 409 | 410 | [[package]] 411 | name = "lucidity-core" 412 | version = "0.1.0" 413 | dependencies = [ 414 | "lunatic-twitchax-patch", 415 | "rand", 416 | "serde", 417 | ] 418 | 419 | [[package]] 420 | name = "lucidity-macros" 421 | version = "0.1.0" 422 | dependencies = [ 423 | "convert_case", 424 | "lucidity-core", 425 | "lunatic-twitchax-patch", 426 | "pretty_assertions", 427 | "proc-macro2", 428 | "quote", 429 | "rand", 430 | "serde", 431 | "syn 2.0.46", 432 | ] 433 | 434 | [[package]] 435 | name = "lunatic" 436 | version = "0.13.1" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "0a81de8cce7e1b8185afdf1ebeef5bfce009b06f8d0b7527d3baddc7f81f0487" 439 | dependencies = [ 440 | "bincode", 441 | "lunatic-macros", 442 | "lunatic-sys 0.13.0", 443 | "lunatic-test", 444 | "paste", 445 | "rustversion", 446 | "serde", 447 | "thiserror", 448 | ] 449 | 450 | [[package]] 451 | name = "lunatic-log" 452 | version = "0.4.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "ac2236f0168dfb4ca227d25c5ed4de879a70fa4c3d11087b86fbc27d29144ce7" 455 | dependencies = [ 456 | "chrono", 457 | "lunatic", 458 | "serde", 459 | "yansi", 460 | ] 461 | 462 | [[package]] 463 | name = "lunatic-macros" 464 | version = "0.13.0" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "5a7ddb2dbdcd909a3f9667b3616d7dc103eea47a641bca1d3bb6fe11b915b798" 467 | dependencies = [ 468 | "convert_case", 469 | "darling", 470 | "proc-macro2", 471 | "quote", 472 | "syn 1.0.109", 473 | ] 474 | 475 | [[package]] 476 | name = "lunatic-sys" 477 | version = "0.13.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "20b8b9023c9664595f6cff816a711beec4e9f50d25ce6ed31eb2465a8232a435" 480 | 481 | [[package]] 482 | name = "lunatic-sys" 483 | version = "0.14.0" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "d572d8b5fa4693714650d42566863cf218bb69119d5d8514129972ee00c75ada" 486 | 487 | [[package]] 488 | name = "lunatic-test" 489 | version = "0.13.0" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "31e0c9aa5a17dba90ef8685013d28589d54f04b028f9b3f58c3223e8bf00827d" 492 | dependencies = [ 493 | "quote", 494 | "syn 1.0.109", 495 | ] 496 | 497 | [[package]] 498 | name = "lunatic-twitchax-patch" 499 | version = "0.14.1" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "502e2ebee49b5a250d89b8918ce490a52773da8a26ec661e45c6a19873820edd" 502 | dependencies = [ 503 | "bincode", 504 | "lunatic-macros", 505 | "lunatic-sys 0.14.0", 506 | "lunatic-test", 507 | "paste", 508 | "rustversion", 509 | "serde", 510 | "thiserror", 511 | ] 512 | 513 | [[package]] 514 | name = "matches" 515 | version = "0.1.10" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" 518 | 519 | [[package]] 520 | name = "mime" 521 | version = "0.3.17" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 524 | 525 | [[package]] 526 | name = "miniz_oxide" 527 | version = "0.7.1" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 530 | dependencies = [ 531 | "adler", 532 | ] 533 | 534 | [[package]] 535 | name = "nightfly" 536 | version = "0.1.6" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "295b7c58b6f32ac19cf52a69e6dbd9e48ff13fe7398e937d68b11d85bc594b65" 539 | dependencies = [ 540 | "base64", 541 | "brotli", 542 | "bytes", 543 | "cookie", 544 | "cookie_store", 545 | "encoding_rs", 546 | "flate2", 547 | "http", 548 | "http-body", 549 | "httparse", 550 | "ipnet", 551 | "lunatic", 552 | "lunatic-log", 553 | "mime", 554 | "percent-encoding", 555 | "proc-macro-hack", 556 | "serde", 557 | "serde_json", 558 | "serde_urlencoded", 559 | "thiserror", 560 | "tower-service", 561 | "url", 562 | ] 563 | 564 | [[package]] 565 | name = "num-traits" 566 | version = "0.2.17" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 569 | dependencies = [ 570 | "autocfg", 571 | ] 572 | 573 | [[package]] 574 | name = "once_cell" 575 | version = "1.19.0" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 578 | 579 | [[package]] 580 | name = "paste" 581 | version = "1.0.14" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" 584 | 585 | [[package]] 586 | name = "percent-encoding" 587 | version = "2.3.1" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 590 | 591 | [[package]] 592 | name = "pin-project-lite" 593 | version = "0.2.13" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 596 | 597 | [[package]] 598 | name = "ppv-lite86" 599 | version = "0.2.17" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 602 | 603 | [[package]] 604 | name = "pretty_assertions" 605 | version = "1.4.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" 608 | dependencies = [ 609 | "diff", 610 | "yansi", 611 | ] 612 | 613 | [[package]] 614 | name = "proc-macro-hack" 615 | version = "0.5.20+deprecated" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" 618 | 619 | [[package]] 620 | name = "proc-macro2" 621 | version = "1.0.74" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" 624 | dependencies = [ 625 | "unicode-ident", 626 | ] 627 | 628 | [[package]] 629 | name = "psl-types" 630 | version = "2.0.11" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" 633 | 634 | [[package]] 635 | name = "publicsuffix" 636 | version = "2.2.3" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" 639 | dependencies = [ 640 | "idna 0.3.0", 641 | "psl-types", 642 | ] 643 | 644 | [[package]] 645 | name = "quote" 646 | version = "1.0.35" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 649 | dependencies = [ 650 | "proc-macro2", 651 | ] 652 | 653 | [[package]] 654 | name = "rand" 655 | version = "0.8.5" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 658 | dependencies = [ 659 | "libc", 660 | "rand_chacha", 661 | "rand_core", 662 | ] 663 | 664 | [[package]] 665 | name = "rand_chacha" 666 | version = "0.3.1" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 669 | dependencies = [ 670 | "ppv-lite86", 671 | "rand_core", 672 | ] 673 | 674 | [[package]] 675 | name = "rand_core" 676 | version = "0.6.4" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 679 | dependencies = [ 680 | "getrandom", 681 | ] 682 | 683 | [[package]] 684 | name = "rustc_version" 685 | version = "0.2.3" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 688 | dependencies = [ 689 | "semver", 690 | ] 691 | 692 | [[package]] 693 | name = "rustversion" 694 | version = "1.0.14" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 697 | 698 | [[package]] 699 | name = "ryu" 700 | version = "1.0.16" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" 703 | 704 | [[package]] 705 | name = "semver" 706 | version = "0.9.0" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 709 | dependencies = [ 710 | "semver-parser", 711 | ] 712 | 713 | [[package]] 714 | name = "semver-parser" 715 | version = "0.7.0" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 718 | 719 | [[package]] 720 | name = "serde" 721 | version = "1.0.194" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" 724 | dependencies = [ 725 | "serde_derive", 726 | ] 727 | 728 | [[package]] 729 | name = "serde_derive" 730 | version = "1.0.194" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" 733 | dependencies = [ 734 | "proc-macro2", 735 | "quote", 736 | "syn 2.0.46", 737 | ] 738 | 739 | [[package]] 740 | name = "serde_json" 741 | version = "1.0.110" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "6fbd975230bada99c8bb618e0c365c2eefa219158d5c6c29610fd09ff1833257" 744 | dependencies = [ 745 | "itoa", 746 | "ryu", 747 | "serde", 748 | ] 749 | 750 | [[package]] 751 | name = "serde_urlencoded" 752 | version = "0.7.1" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 755 | dependencies = [ 756 | "form_urlencoded", 757 | "itoa", 758 | "ryu", 759 | "serde", 760 | ] 761 | 762 | [[package]] 763 | name = "sha1" 764 | version = "0.6.1" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" 767 | dependencies = [ 768 | "sha1_smol", 769 | ] 770 | 771 | [[package]] 772 | name = "sha1_smol" 773 | version = "1.0.0" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" 776 | 777 | [[package]] 778 | name = "standback" 779 | version = "0.2.17" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" 782 | dependencies = [ 783 | "version_check", 784 | ] 785 | 786 | [[package]] 787 | name = "stdweb" 788 | version = "0.4.20" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" 791 | dependencies = [ 792 | "discard", 793 | "rustc_version", 794 | "stdweb-derive", 795 | "stdweb-internal-macros", 796 | "stdweb-internal-runtime", 797 | "wasm-bindgen", 798 | ] 799 | 800 | [[package]] 801 | name = "stdweb-derive" 802 | version = "0.5.3" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" 805 | dependencies = [ 806 | "proc-macro2", 807 | "quote", 808 | "serde", 809 | "serde_derive", 810 | "syn 1.0.109", 811 | ] 812 | 813 | [[package]] 814 | name = "stdweb-internal-macros" 815 | version = "0.2.9" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" 818 | dependencies = [ 819 | "base-x", 820 | "proc-macro2", 821 | "quote", 822 | "serde", 823 | "serde_derive", 824 | "serde_json", 825 | "sha1", 826 | "syn 1.0.109", 827 | ] 828 | 829 | [[package]] 830 | name = "stdweb-internal-runtime" 831 | version = "0.1.5" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" 834 | 835 | [[package]] 836 | name = "strsim" 837 | version = "0.10.0" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 840 | 841 | [[package]] 842 | name = "syn" 843 | version = "1.0.109" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 846 | dependencies = [ 847 | "proc-macro2", 848 | "quote", 849 | "unicode-ident", 850 | ] 851 | 852 | [[package]] 853 | name = "syn" 854 | version = "2.0.46" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e" 857 | dependencies = [ 858 | "proc-macro2", 859 | "quote", 860 | "unicode-ident", 861 | ] 862 | 863 | [[package]] 864 | name = "thiserror" 865 | version = "1.0.53" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "b2cd5904763bad08ad5513ddbb12cf2ae273ca53fa9f68e843e236ec6dfccc09" 868 | dependencies = [ 869 | "thiserror-impl", 870 | ] 871 | 872 | [[package]] 873 | name = "thiserror-impl" 874 | version = "1.0.53" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "3dcf4a824cce0aeacd6f38ae6f24234c8e80d68632338ebaa1443b5df9e29e19" 877 | dependencies = [ 878 | "proc-macro2", 879 | "quote", 880 | "syn 2.0.46", 881 | ] 882 | 883 | [[package]] 884 | name = "time" 885 | version = "0.2.27" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" 888 | dependencies = [ 889 | "const_fn", 890 | "libc", 891 | "standback", 892 | "stdweb", 893 | "time-macros", 894 | "version_check", 895 | "winapi", 896 | ] 897 | 898 | [[package]] 899 | name = "time-macros" 900 | version = "0.1.1" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" 903 | dependencies = [ 904 | "proc-macro-hack", 905 | "time-macros-impl", 906 | ] 907 | 908 | [[package]] 909 | name = "time-macros-impl" 910 | version = "0.1.2" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" 913 | dependencies = [ 914 | "proc-macro-hack", 915 | "proc-macro2", 916 | "quote", 917 | "standback", 918 | "syn 1.0.109", 919 | ] 920 | 921 | [[package]] 922 | name = "tinyvec" 923 | version = "1.6.0" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 926 | dependencies = [ 927 | "tinyvec_macros", 928 | ] 929 | 930 | [[package]] 931 | name = "tinyvec_macros" 932 | version = "0.1.1" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 935 | 936 | [[package]] 937 | name = "tower-service" 938 | version = "0.3.2" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 941 | 942 | [[package]] 943 | name = "unicode-bidi" 944 | version = "0.3.14" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" 947 | 948 | [[package]] 949 | name = "unicode-ident" 950 | version = "1.0.12" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 953 | 954 | [[package]] 955 | name = "unicode-normalization" 956 | version = "0.1.22" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 959 | dependencies = [ 960 | "tinyvec", 961 | ] 962 | 963 | [[package]] 964 | name = "unicode-segmentation" 965 | version = "1.10.1" 966 | source = "registry+https://github.com/rust-lang/crates.io-index" 967 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 968 | 969 | [[package]] 970 | name = "url" 971 | version = "2.5.0" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 974 | dependencies = [ 975 | "form_urlencoded", 976 | "idna 0.5.0", 977 | "percent-encoding", 978 | "serde", 979 | ] 980 | 981 | [[package]] 982 | name = "version_check" 983 | version = "0.9.4" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 986 | 987 | [[package]] 988 | name = "wasi" 989 | version = "0.11.0+wasi-snapshot-preview1" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 992 | 993 | [[package]] 994 | name = "wasm-bindgen" 995 | version = "0.2.89" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" 998 | dependencies = [ 999 | "cfg-if", 1000 | "wasm-bindgen-macro", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "wasm-bindgen-backend" 1005 | version = "0.2.89" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" 1008 | dependencies = [ 1009 | "bumpalo", 1010 | "log", 1011 | "once_cell", 1012 | "proc-macro2", 1013 | "quote", 1014 | "syn 2.0.46", 1015 | "wasm-bindgen-shared", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "wasm-bindgen-macro" 1020 | version = "0.2.89" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" 1023 | dependencies = [ 1024 | "quote", 1025 | "wasm-bindgen-macro-support", 1026 | ] 1027 | 1028 | [[package]] 1029 | name = "wasm-bindgen-macro-support" 1030 | version = "0.2.89" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" 1033 | dependencies = [ 1034 | "proc-macro2", 1035 | "quote", 1036 | "syn 2.0.46", 1037 | "wasm-bindgen-backend", 1038 | "wasm-bindgen-shared", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "wasm-bindgen-shared" 1043 | version = "0.2.89" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" 1046 | 1047 | [[package]] 1048 | name = "winapi" 1049 | version = "0.3.9" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1052 | dependencies = [ 1053 | "winapi-i686-pc-windows-gnu", 1054 | "winapi-x86_64-pc-windows-gnu", 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "winapi-i686-pc-windows-gnu" 1059 | version = "0.4.0" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1062 | 1063 | [[package]] 1064 | name = "winapi-x86_64-pc-windows-gnu" 1065 | version = "0.4.0" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1068 | 1069 | [[package]] 1070 | name = "windows-core" 1071 | version = "0.52.0" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 1074 | dependencies = [ 1075 | "windows-targets 0.52.0", 1076 | ] 1077 | 1078 | [[package]] 1079 | name = "windows-targets" 1080 | version = "0.48.5" 1081 | source = "registry+https://github.com/rust-lang/crates.io-index" 1082 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1083 | dependencies = [ 1084 | "windows_aarch64_gnullvm 0.48.5", 1085 | "windows_aarch64_msvc 0.48.5", 1086 | "windows_i686_gnu 0.48.5", 1087 | "windows_i686_msvc 0.48.5", 1088 | "windows_x86_64_gnu 0.48.5", 1089 | "windows_x86_64_gnullvm 0.48.5", 1090 | "windows_x86_64_msvc 0.48.5", 1091 | ] 1092 | 1093 | [[package]] 1094 | name = "windows-targets" 1095 | version = "0.52.0" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 1098 | dependencies = [ 1099 | "windows_aarch64_gnullvm 0.52.0", 1100 | "windows_aarch64_msvc 0.52.0", 1101 | "windows_i686_gnu 0.52.0", 1102 | "windows_i686_msvc 0.52.0", 1103 | "windows_x86_64_gnu 0.52.0", 1104 | "windows_x86_64_gnullvm 0.52.0", 1105 | "windows_x86_64_msvc 0.52.0", 1106 | ] 1107 | 1108 | [[package]] 1109 | name = "windows_aarch64_gnullvm" 1110 | version = "0.48.5" 1111 | source = "registry+https://github.com/rust-lang/crates.io-index" 1112 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1113 | 1114 | [[package]] 1115 | name = "windows_aarch64_gnullvm" 1116 | version = "0.52.0" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 1119 | 1120 | [[package]] 1121 | name = "windows_aarch64_msvc" 1122 | version = "0.48.5" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1125 | 1126 | [[package]] 1127 | name = "windows_aarch64_msvc" 1128 | version = "0.52.0" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 1131 | 1132 | [[package]] 1133 | name = "windows_i686_gnu" 1134 | version = "0.48.5" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1137 | 1138 | [[package]] 1139 | name = "windows_i686_gnu" 1140 | version = "0.52.0" 1141 | source = "registry+https://github.com/rust-lang/crates.io-index" 1142 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 1143 | 1144 | [[package]] 1145 | name = "windows_i686_msvc" 1146 | version = "0.48.5" 1147 | source = "registry+https://github.com/rust-lang/crates.io-index" 1148 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1149 | 1150 | [[package]] 1151 | name = "windows_i686_msvc" 1152 | version = "0.52.0" 1153 | source = "registry+https://github.com/rust-lang/crates.io-index" 1154 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 1155 | 1156 | [[package]] 1157 | name = "windows_x86_64_gnu" 1158 | version = "0.48.5" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1161 | 1162 | [[package]] 1163 | name = "windows_x86_64_gnu" 1164 | version = "0.52.0" 1165 | source = "registry+https://github.com/rust-lang/crates.io-index" 1166 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 1167 | 1168 | [[package]] 1169 | name = "windows_x86_64_gnullvm" 1170 | version = "0.48.5" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1173 | 1174 | [[package]] 1175 | name = "windows_x86_64_gnullvm" 1176 | version = "0.52.0" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 1179 | 1180 | [[package]] 1181 | name = "windows_x86_64_msvc" 1182 | version = "0.48.5" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1185 | 1186 | [[package]] 1187 | name = "windows_x86_64_msvc" 1188 | version = "0.52.0" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 1191 | 1192 | [[package]] 1193 | name = "yansi" 1194 | version = "0.5.1" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" 1197 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["lucidity-core", "lucidity-macros", "lucidity"] -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # lucidity development 2 | 3 | ## Publish to Cargo 4 | 5 | Make sure `lucidity` references the correct versions of `lucidity-core` and `lucidity-macros` in `Cargo.toml`. 6 | 7 | ```bash 8 | $ cargo publish -p lucidity-core 9 | $ cargo publish -p lucidity-macros 10 | $ cargo publish -p lucidity 11 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Aaron Roney 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build and Test](https://github.com/twitchax/lucidity/actions/workflows/build.yml/badge.svg)](https://github.com/twitchax/lucidity/actions/workflows/build.yml) 2 | [![Version](https://img.shields.io/crates/v/lucidity.svg)](https://crates.io/crates/lucidity) 3 | [![Crates.io](https://img.shields.io/crates/d/lucidity?label=crate)](https://crates.io/crates/lucidity) 4 | [![Documentation](https://docs.rs/lucidity/badge.svg)](https://docs.rs/lucidity) 5 | [![Rust](https://img.shields.io/badge/rust-nightly-blue.svg?maxAge=3600)](https://github.com/twitchax/rtz) 6 | [![License:MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 7 | 8 | # lucidity 9 | 10 | A distributed execution engine built upon [lunatic](https://github.com/lunatic-solutions/lunatic). 11 | 12 | ## Motivation 13 | 14 | Basically, `lunatic` by itself is a set of "low-level" features: runtime, syscalls, and language-wrappers. 15 | 16 | However, the `Process` architecture is a bit harder to use when trying to keep code readable. This library provides a `proc-macro`, and, eventually, some helpers for common platforms like fly.io, to make it easier to write distributed code 17 | on top of the excellent lunatic runtime. 18 | 19 | ### Example 20 | 21 | Here is a simple example below. 22 | 23 | ```rust 24 | fn main() { 25 | let results = pythagorean_remote_fanout(vec![ 26 | (3, 4), 27 | (4, 5), 28 | (5, 6), 29 | (6, 7), 30 | (7, 8), 31 | (8, 9), 32 | (9, 10), 33 | ]); 34 | 35 | println!("result: {:#?}", results); 36 | } 37 | 38 | #[lucidity::job] 39 | fn pythagorean(a: u32, b: u32) -> f32 { 40 | let num = ((square_remote_async(a).await_get() + square_remote_async(b).await_get()) as f32).sqrt(); 41 | 42 | num 43 | } 44 | 45 | #[lucidity::job] 46 | fn square(a: u32) -> u32 { 47 | let num = a * a; 48 | 49 | num 50 | } 51 | ``` 52 | 53 | For each method you place the proc macro (`lucidity::job`) on, we generate a few others. 54 | 55 | * `{name}_local`, when called, spawns the function in a _node local_ `Process`, and blocks the calling `Process`. 56 | * `{name}_remote`, when called, spawns the function in a `Process` on a random _distributed node_, and blocks the calling `Process`. 57 | * `{name}_local_async`, when called, spawns the function in a _node local_ `Process`, handing back a wrapped reference to the `Process`, which can be polled, or blocked upon. 58 | * `{name}_remote_async`, when called, spawns the function in a `Process` on a random _distributed node_, handing back a wrapped reference to the `Process`, which can be polled, or blocked upon. 59 | * `{name}_remote_fanout`, which takes a `Vec` of arg tuples and roundrobin distributes calls to that function with those arguments, polling all of the `Process`es, and blocking until all are complete, returning a `Vec` of the results. 60 | 61 | The above example uses the `lucidity::job` proc macro to generate a few of those functions, and they can be "called" like any other function. The goal here is to use the excellent architecture of `lunatic`, while cutting down on some of the 62 | boilerplate required to successfully write the distributed code. Setting up the `Process`es, and the `Mailbox`es, etc., is all handled for you. 63 | The tradeoff is that this library is opinionated about how you write your code, and what you can do with it (open to suggestions, though). In addition, this library introduces some simple loops with timeouts to avoid possible deadlock, 64 | which has some overhead. 65 | 66 | ## Library Usage 67 | 68 | First, install lunatic. 69 | 70 | ```bash 71 | $ cargo install lunatic-runtime 72 | ``` 73 | 74 | Add this to your `Cargo.toml`: 75 | 76 | ```toml 77 | [dependencies] 78 | lucidity = "*" # choose a version 79 | ``` 80 | 81 | In your `.cargo/config.toml`: 82 | 83 | ```toml 84 | [build] 85 | target = "wasm32-wasi" 86 | 87 | [target.wasm32-wasi] 88 | runner = "lunatic run" 89 | ``` 90 | 91 | ### Distributed Setup 92 | 93 | To use this library in a distributed setup, you will need to do a few things. This example could easily be used locally 94 | as well, by just using the loopback address. 95 | 96 | First, you need to run the control node somewhere. 97 | 98 | ```bash 99 | $ lunatic control --bind-socket [::]:3030 100 | ``` 101 | 102 | And, on any other machines where you want the remote methods to run, you will need to set up nodes. 103 | 104 | ```bash 105 | $ lunatic node --bind-socket [::]:3031 http://{IP_OR_HOST_OF_CONTROL}:3030/ 106 | ``` 107 | 108 | #### Local Testing 109 | 110 | For testing, you would then build your code and run it inside a `lunatic` node. Something like this. 111 | 112 | ```bash 113 | cargo build --release && lunatic node --wasm path/to/built/wasm/exe.wasm --bind-socket [::]:3032 http://{IP_OR_HOST_OF_CONTROL}:3030/ 114 | ``` 115 | 116 | #### Production Setup 117 | 118 | In a more production setup, you would probably use something like `fly.io` to deploy your code (use the `fly` feature), and you may want 119 | to build and run your code in a container. The easiest way is a simple docker container that runs the control node, and the application 120 | node. Your entry point would look something like this. 121 | 122 | ```bash 123 | #!bin/bash 124 | 125 | /lunatic control --bind-socket [::]:3030 & 126 | 127 | /lunatic node --wasm /irl_processor.wasm --bind-socket $NODE_REACHABLE_IP:3031 http://[::1]:3030/ 128 | ``` 129 | 130 | Then, within your built wasm, you would span some nodes that would connect to the control node on other machines 131 | before running any of the distributed methods. 132 | 133 | ## `lunatic` Primer 134 | 135 | This library is built on top of `lunatic`, so it is important to understand the basics of `lunatic` before using this library. 136 | 137 | ### Processes 138 | 139 | `lunatic` is built around the concept of `Processes`. A `Process` is a lightweight thread of execution that is spawned 140 | by the runtime. Each `Process` has its own stack, and is isolated from other `Process`es. `Process`es communicate 141 | with each other via `Mailbox`es, which are essentially queues that can be used to send messages between `Process`es. 142 | 143 | In the case of this library, you can totally use `Process`es directly, but the point of the `lucidity` library is to 144 | make it easier to write distributed code, so we will focus on that. 145 | 146 | ### Mailboxes 147 | 148 | `Mailbox`es are the primary way that `Process`es communicate with each other. A `Mailbox` is a queue that can be used 149 | to send messages between `Process`es. Each `Process` has a `Mailbox` that can be used to send messages to that `Process`. 150 | 151 | For the purposes of this library, you don't need to worry about `Mailbox`es, as they are handled for you. However, it is 152 | important to know that they exist since the "syntactic sugar" provided by this library abstracts away these mssage queues. 153 | This is not like "async Rust", or any other "async/await" type languages. These `Process`es, and their `Mailbox`es, are 154 | more like the coroutine or goroutine behavior of other languages. 155 | 156 | As such, this library adds some overhead in the way it "feels" sort of like async Rust or blocking Rust, but it achieves 157 | that feel by using timeouts with wait loops. As this project is meant more for "fanning out" rigorous work to other nodes, this 158 | overhead is acceptable, but it is important to understand that this is not like "async Rust". 159 | 160 | ### WASM 161 | 162 | `lunatic` is built around the concept of WebAssembly (WASM). WASM is a binary format that is meant to be run in a sandboxed 163 | environment. `lunatic` is able to scale so well to a distributed model because it relies on the concept that the "runtime" 164 | ships with a WASM runtime, while that WASM code can make certain "runtime syscalls" for communication. The WASM abstracts 165 | away the machine code such that each node can function properly with just the WASM from another node. 166 | 167 | Theoretically, multiple nodes could each be initialized with their own WASM, and the `lunatic` runtime would be able to 168 | spawn `Process`es on any of those nodes, as each node would send its WASM to the other nodes. 169 | 170 | ### Remote Processes 171 | 172 | `Process`es that are spawed remotely take advantage of the fact that your executable _is_ WASM. Basically, `lunatic` 173 | sends a copy of your WASM executable to the remote node, and then spawns a `Process` there, essentially using function 174 | pointers to call the functions in your WASM executable. This is why you need to build your code as WASM, and why you 175 | need to run the control node, and the application node, with the same executable. 176 | 177 | However, you don't need to worry about getting your code onto _other_ nodes. The `lunatic` runtime handles this automatically. 178 | This also means that your "bare" functions "just work". That function is in the WASM, so if a process calls that function, 179 | it will be called on the node where the process is running since that node _has_ the WASM. 180 | 181 | Pretty cool, right? 182 | 183 | ## Examples 184 | 185 | Let's look at a few examples to understand when you would use specific types of methods. 186 | 187 | For all of these examples, we can assume that we have declared the `square` function like this. 188 | 189 | ```rust 190 | #[lucidity::job] 191 | fn square(a: u32) -> u32 { 192 | a * a 193 | } 194 | ``` 195 | 196 | ### "No Process" 197 | 198 | Even if you mark a function with the `lucidity::job` proc macro, you can still call it like a normal function. 199 | 200 | ```rust 201 | fn main() { 202 | // Calling `square` here does not span a process, and is called by the currently executing process 203 | // as if it were a normal function. 204 | let result = square(3); 205 | 206 | println!("result: {:#?}", result); 207 | } 208 | ``` 209 | 210 | ### Local / Remote Process 211 | 212 | If you want to spawn a process locally, you can use the `{name}_local` method. 213 | 214 | ```rust 215 | fn main() { 216 | // The `remote_fanout` is discussed below, but this is sort of the main meat and potatoes. 217 | // This function will be called on a set of nodes (round-robin-ed), and each of those nodes 218 | // will run it in a process. 219 | let results = pythagorean1_remote_fanout(vec![ 220 | (3, 4), 221 | (4, 5), 222 | (5, 6), 223 | (6, 7), 224 | (7, 8), 225 | (8, 9), 226 | (9, 10), 227 | ]); 228 | 229 | println!("result: {:#?}", results); 230 | } 231 | 232 | #[lucidity::job] 233 | fn pythagorean1(a: u32, b: u32) -> f32 { 234 | // Calling `square_local` here spawns a process locally, and blocks the current process until the 235 | // spawned process completes. This is great for allowing the `lunatic` executor to "yield" more often, 236 | // especially if the `square` method had reasonable yield points. However, the process it is called from is blocked, 237 | // so keep that in mind. 238 | // 239 | // In this case, we don't really mind blocking here, since we are in a lightweight process. However, you may notice 240 | // there is an inefficiency in not computing the square of `a` and `b` in parallel. 241 | ((square_local(a) + square_local(b)) as f32).sqrt() 242 | } 243 | 244 | #[lucidity::job] 245 | fn pythagorean2(a: u32, b: u32) -> f32 { 246 | // Calling `square_remote` here spawns a process on a random remote node, and blocks the current process until the 247 | // spawned process completes. This is great for ensuring a certain set of processes are being distributed, 248 | // but blocks the process it is called from. 249 | ((square_remote(a) + square_remote(b)) as f32).sqrt() 250 | } 251 | ``` 252 | 253 | ### Local / Remote Async Process 254 | 255 | If you want more fine-grained control over when to block, you can use the `{name}_local_async` and `{name}_remote_async` methods. 256 | 257 | ```rust 258 | fn main() { 259 | // The `remote_fanout` is discussed below, but this is sort of the main meat and potatoes. 260 | // This function will be called on a set of nodes (round-robin-ed), and each of those nodes 261 | // will run it in a process. 262 | let results = pythagorean1_remote_fanout(vec![ 263 | (3, 4), 264 | (4, 5), 265 | (5, 6), 266 | (6, 7), 267 | (7, 8), 268 | (8, 9), 269 | (9, 10), 270 | ]); 271 | 272 | println!("result: {:#?}", results); 273 | } 274 | 275 | 276 | #[lucidity::job] 277 | fn pythagorean1(a: u32, b: u32) -> f32 { 278 | // This spawns a local process, and hands back a `Job` that can be polled, or blocked upon. 279 | // Here, we are going to block with `await_get`. This can be used for either local or remote 280 | // async jobs. 281 | let square_a_job = square_local_async(a); 282 | let square_b_job = square_local_async(b); 283 | 284 | // We get to this point "immediately". The `square_a_job` and `square_b_job` are running in 285 | // their own processes, and we can do other work here. 286 | 287 | // Maybe do some other work in here ... 288 | 289 | let mut square_a = square_a_job.await_get(); 290 | let mut square_b = square_b_job.await_get(); 291 | 292 | ((square_a + square_b) as f32).sqrt() 293 | } 294 | 295 | #[lucidity::job] 296 | fn pythagorean2(a: u32, b: u32) -> f32 { 297 | // This spawns a local process, and hands back a `Job` that can be polled, or blocked upon. 298 | // Here, we are going to loop and check for completion with `try_get` (sort of naively). This can be used for either local or remote 299 | // async jobs. 300 | let square_a_job = square_remote_async(a); 301 | let square_b_job = square_remote_async(b); 302 | 303 | // We get to this point "immediately". The `square_a_job` and `square_b_job` are running in 304 | // their own processes, and we can do other work here. 305 | 306 | // Maybe do your own looping ... 307 | let (square_a, square_b) = loop { 308 | let Some(a) = square_a_job.try_get() else { 309 | continue; 310 | } 311 | 312 | let Some(b) = square_b_job.try_get() else { 313 | continue; 314 | } 315 | 316 | break (a, b); 317 | } 318 | 319 | ((square_a + square_b) as f32).sqrt() 320 | } 321 | ``` 322 | 323 | ### Remote Fanout 324 | 325 | If you essentially want to do the same operation, but with different arguments, and you want to block on all of them, 326 | you can use the `{name}_remote_fanout` method. 327 | 328 | ```rust 329 | fn main() { 330 | // The `remote_fanout` is discussed below, but this is sort of the main meat and potatoes. 331 | // This function will be called on a set of nodes (round-robin-ed), and each of those nodes 332 | // will run it in a process. 333 | // 334 | // This is great for ensuring a certain set of processes are being distributed, but blocks the process it is called from. 335 | // You may have something where you are processing a set of images. This would be a great use case to put those images 336 | // in a `Vec`, and then call this method to fan out all of the work. 337 | let results = square_remote_fanout(vec![1, 2, 3, 4, 5]); 338 | 339 | println!("result: {:#?}", results); 340 | } 341 | ``` 342 | 343 | ## Job Attribute Options 344 | 345 | The `lucidity::job` proc macro has a few options that can be used to customize the behavior of the generated methods. 346 | 347 | Generally, this do not need to be used, but they are available if you need them. 348 | 349 | * `init_retry_interval_ms`: This is the number of milliseconds to wait between retries when trying to initialize a `Process`. Defaults to `100`. 350 | * `sync_retry_interval_ms`: This is the number of milliseconds to wait between retries when trying to get a blocking (e.g., `{name}_local` or `{name}_remote`) from a `Process`. Defaults to `100`. 351 | * `async_init_retry_interval_ms`: This is the number of milliseconds to wait between retries when trying to initialize a `Process` asynchronously (e.g., `{name}_local_async` or `{name}_remote_async`). Defaults to `100`. 352 | * `async_get_retry_interval_ms`: This is the number of milliseconds to wait between retries when trying to get a non-blocking result (e.g., `{name}_local_async` or `{name}_remote_async`) from a `Process`. Defaults to `100`. 353 | * `async_set_retry_interval_ms`: This is the number of milliseconds to wait between retries when the execution `Process` attempts to set a non-blocking result (e.g., `{name}_local_async` or `{name}_remote_async`) from a `Process`. Defaults to `100`. 354 | * `shutdown_retry_interval_ms`: This is the number of milliseconds to wait between retries when trying to shutdown a `Process`. Defaults to `100`. 355 | * `memory`: This is the amount of maximum memory allowed to the `Process`. Defaults to `100 * 1024 * 1024` (100MB). 356 | * `fuel`: This is the amount of maximum fuel allowed to the `Process`. Defaults to `10` (each unit of fuel is approximately 100,000 WASM instructions). 357 | * `fanout`: This is the type of scheme to use when fanning out. Defaults to `roundrobin`. The other option is `random`. 358 | 359 | ## Feature Flags 360 | 361 | * `fly`: This enables the `fly` feature, which allows you to use the `fly.io` platform to automatically set up nodes 362 | from the main `lunatic` node. This is not enabled by default, as it requires a `fly.io` account, and a bit of setup. 363 | See the `fly.io` documentation for more information. 364 | 365 | ## Test 366 | 367 | ```bash 368 | cargo test 369 | ``` 370 | 371 | ## Thanks 372 | 373 | Special thanks to the [lunatic](https://github.com/lunatic-solutions/lunatic)'s authors and contributors for their excellent work, 374 | and special thanks to the primary author, [bkolobara](https://github.com/bkolobara). 375 | 376 | ## License 377 | 378 | MIT -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | 3 | RUN apt-get update && apt-get install -y curl sqlite3 iproute2 4 | 5 | RUN curl -L -O https://github.com/lunatic-solutions/lunatic/releases/download/v0.13.2/lunatic-linux-amd64.tar.gz 6 | RUN tar -xzf lunatic-linux-amd64.tar.gz 7 | #COPY ./docker/lunatic /lunatic 8 | 9 | COPY ./docker/app.entrypoint.sh . 10 | RUN chmod a+x app.entrypoint.sh 11 | 12 | ENTRYPOINT [ "/app.entrypoint.sh" ] -------------------------------------------------------------------------------- /docker/app.entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!bin/bash 2 | 3 | /lunatic node --bind-socket [$FLY_PRIVATE_IP]:3031 $1 -------------------------------------------------------------------------------- /docker/lunatic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitchax/lucidity/85d3121df86df5d2928ddf27f01e2356e8b6ef72/docker/lunatic -------------------------------------------------------------------------------- /lucidity-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lucidity-core" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Aaron Roney "] 6 | license = "MIT" 7 | description = "A distributed orchestrator platform for Rust." 8 | readme = "../README.md" 9 | homepage = "https://github.com/twitchax/lucidity" 10 | repository = "https://github.com/twitchax/lucidity" 11 | keywords = ["lucidity", "orchestrator", "distributed", "macro"] 12 | categories = ["orchestration"] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | #lunatic = { git = "https://github.com/twitchax/lunatic-rs" } 18 | lunatic = { package = "lunatic-twitchax-patch", version = "0.14.1" } 19 | serde = "1.0.193" 20 | rand = "0.8.5" -------------------------------------------------------------------------------- /lucidity-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The `lucidity-core` crate. Abstracts away core functionality, and functionality for build scripts. 2 | 3 | #![warn(rustdoc::broken_intra_doc_links, rust_2018_idioms, clippy::all, missing_docs)] 4 | 5 | use core::time::Duration; 6 | 7 | use lunatic::{ap::ProcessRef, AbstractProcess}; 8 | 9 | pub use lunatic; 10 | pub use rand; 11 | pub use serde; 12 | 13 | /// A job is a process that can be spawned and shutdown. 14 | /// 15 | /// This type is usually created with the [`lucidity::job`] macro on the async methods. 16 | pub struct Job 17 | where 18 | T: AbstractProcess, 19 | { 20 | /// The process reference. 21 | pub process: ProcessRef, 22 | } 23 | 24 | impl Drop for Job 25 | where 26 | T: AbstractProcess, 27 | { 28 | fn drop(&mut self) { 29 | loop { 30 | if let Ok(r) = self.process.with_timeout(Duration::from_millis(100)).shutdown() { 31 | break r; 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lucidity-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lucidity-macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Aaron Roney "] 6 | license = "MIT" 7 | description = "A distributed orchestrator platform for Rust." 8 | readme = "../README.md" 9 | homepage = "https://github.com/twitchax/lucidity" 10 | repository = "https://github.com/twitchax/lucidity" 11 | keywords = ["lucidity", "orchestrator", "distributed", "macro"] 12 | categories = ["orchestration"] 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 18 | 19 | [dependencies] 20 | lucidity-core = { path = "../lucidity-core", version = "0.1.0" } 21 | 22 | pretty_assertions = "1.2.1" 23 | 24 | proc-macro2 = "1.0.71" 25 | syn = { version = "2.0.43", features = ["full"] } 26 | quote = "1.0.33" 27 | convert_case = "0.6.0" 28 | 29 | #lunatic = { git = "https://github.com/twitchax/lunatic-rs" } 30 | lunatic = { package = "lunatic-twitchax-patch", version = "0.14.1" } 31 | serde = "1.0.193" 32 | rand = "0.8.5" -------------------------------------------------------------------------------- /lucidity-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The `lucidity-macros` crate. Provides the `job` macro. 2 | 3 | #![warn(rustdoc::broken_intra_doc_links, rust_2018_idioms, clippy::all, missing_docs)] 4 | 5 | use core::panic; 6 | use std::str::FromStr; 7 | 8 | use convert_case::{Case, Casing}; 9 | use proc_macro2::{Literal, TokenStream, TokenTree}; 10 | use quote::quote; 11 | use syn::{Ident, ItemFn}; 12 | 13 | /// The `job` macro. 14 | /// 15 | /// This macro creates a local and remote version of the function, as well as a local and remote async version of the function. 16 | /// In addition, it creates the underlying [`AbstractProcess`] and [`Job`] types. 17 | #[proc_macro_attribute] 18 | pub fn job(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { 19 | job_inner(TokenStream::from(attr), TokenStream::from(item)).into() 20 | } 21 | 22 | fn job_inner(attr: TokenStream, item: TokenStream) -> TokenStream { 23 | let input = syn::parse2::(item).unwrap(); 24 | 25 | // Parse code, and get identifiers. 26 | 27 | let name = &input.sig.ident; 28 | let vis = &input.vis; 29 | let return_block = &input.sig.output; 30 | let return_type = match return_block { 31 | syn::ReturnType::Default => quote! { () }, 32 | syn::ReturnType::Type(_, ty) => quote! { #ty }, 33 | }; 34 | let name_pascal = Ident::new(&name.to_string().to_case(Case::Pascal), name.span()); 35 | 36 | // Get argument information. 37 | 38 | let arguments = &input.sig.inputs; 39 | let arguments_names = arguments.iter().map(|arg| match arg { 40 | syn::FnArg::Typed(pat_type) => match &*pat_type.pat { 41 | syn::Pat::Ident(ident) => { 42 | let name = &ident.ident; 43 | quote! { #name } 44 | } 45 | _ => panic!("Invalid argument pattern."), 46 | }, 47 | _ => panic!("Invalid argument pattern."), 48 | }); 49 | let arguments_cloned_names = arguments.iter().map(|arg| match arg { 50 | syn::FnArg::Typed(pat_type) => match &*pat_type.pat { 51 | syn::Pat::Ident(ident) => { 52 | let name = &ident.ident; 53 | quote! { #name.clone() } 54 | } 55 | _ => panic!("Invalid argument pattern."), 56 | }, 57 | _ => panic!("Invalid argument pattern."), 58 | }); 59 | let arguments_types = arguments.iter().map(|arg| match arg { 60 | syn::FnArg::Typed(pat_type) => { 61 | let ty = &pat_type.ty; 62 | quote! { #ty } 63 | } 64 | _ => panic!("Invalid argument pattern."), 65 | }); 66 | 67 | // Get argument helpers for `quote!`. 68 | 69 | let closure_arguments = quote! { #(#arguments_names),* }; 70 | let call_arguments = quote! { #(#arguments_cloned_names),* }; 71 | let arguments_types_list = if arguments.is_empty() { 72 | quote! { () } 73 | } else if arguments.len() == 1 { 74 | quote! { #(#arguments_types),* } 75 | } else { 76 | quote! { (#(#arguments_types),*) } 77 | }; 78 | let option_return_type = quote! { Option<#return_type> }; 79 | let arguments_args_tuple = if arguments.is_empty() { 80 | quote! {} 81 | } else if arguments.len() == 1 { 82 | quote! { args.clone() } 83 | } else { 84 | let a = arguments.iter().enumerate().map(|(k, _)| { 85 | let s = Literal::usize_unsuffixed(k); 86 | quote! { args.#s.clone() } 87 | }); 88 | 89 | quote! { #(#a),* } 90 | }; 91 | let arguments_args_tuple_list = quote! { #arguments_args_tuple }; 92 | 93 | // Names of generated identifiers. 94 | 95 | let service_name_ident = Ident::new(&format!("{}Service", name_pascal), name_pascal.span()); 96 | let job_name_ident = Ident::new(&format!("{}Job", name_pascal), name_pascal.span()); 97 | 98 | let local_fn_ident = Ident::new(&format!("{}_local", name), name.span()); 99 | let remote_fn_ident = Ident::new(&format!("{}_remote", name), name.span()); 100 | let local_async_fn_ident = Ident::new(&format!("{}_local_async", name), name.span()); 101 | let remote_async_fn_ident = Ident::new(&format!("{}_remote_async", name), name.span()); 102 | let remote_fanout_fn_ident = Ident::new(&format!("{}_remote_fanout", name), name.span()); 103 | 104 | let get_ident = Ident::new(&format!("{}_get", name), name.span()); 105 | let set_ident = Ident::new(&format!("{}_set", name), name.span()); 106 | let try_get_ident = Ident::new(&format!("{}_try_get", name), name.span()); 107 | let async_init_ident = Ident::new(&format!("{}_init_async", name), name.span()); 108 | 109 | // Parse the attributes. 110 | 111 | let attr = attr.into_iter().collect::>(); 112 | let attr = attr 113 | .split(|t| match t { 114 | TokenTree::Punct(punct) => punct.as_char() == ',', 115 | _ => false, 116 | }) 117 | .map(|tt| { 118 | let tt = tt.iter().collect::>(); 119 | tt.split(|t| match t { 120 | TokenTree::Punct(punct) => punct.as_char() == '=', 121 | _ => false, 122 | }) 123 | .flat_map(ToOwned::to_owned) 124 | .collect::>() 125 | }) 126 | .filter_map(|v| { 127 | if v.len() != 2 { 128 | return None; 129 | } 130 | 131 | Some((v[0], v[1])) 132 | }) 133 | .collect::>(); 134 | 135 | // Set the attributes. 136 | 137 | let mut init_retry_interval_ms = 100; 138 | let mut sync_retry_interval_ms = 100; 139 | let mut async_init_retry_interval_ms = 100; 140 | let mut async_get_retry_interval_ms = 100; 141 | let mut async_set_retry_interval_ms = 100; 142 | let mut shutdown_retry_interval_ms = 100; 143 | let mut memory = 100u64 * 1024 * 1024; 144 | let mut fuel = 10u64; 145 | let mut fanout = Literal::from_str("\"roundrobin\"").unwrap(); 146 | for (key, value) in attr { 147 | let key = key.to_string(); 148 | let key = key.as_str(); 149 | 150 | match key { 151 | "init_retry_interval_ms" => { 152 | let value = value.to_string(); 153 | let value = value.as_str(); 154 | 155 | match value.parse::() { 156 | Ok(v) => { 157 | init_retry_interval_ms = v; 158 | } 159 | Err(_) => panic!("Invalid attribute argument value `{}`.", value), 160 | } 161 | } 162 | "sync_retry_interval_ms" => { 163 | let value = value.to_string(); 164 | let value = value.as_str(); 165 | 166 | match value.parse::() { 167 | Ok(v) => { 168 | sync_retry_interval_ms = v; 169 | } 170 | Err(_) => panic!("Invalid attribute argument value `{}`.", value), 171 | } 172 | } 173 | "async_init_retry_interval_ms" => { 174 | let value = value.to_string(); 175 | let value = value.as_str(); 176 | 177 | match value.parse::() { 178 | Ok(v) => { 179 | async_init_retry_interval_ms = v; 180 | } 181 | Err(_) => panic!("Invalid attribute argument value `{}`.", value), 182 | } 183 | } 184 | "async_get_retry_interval_ms" => { 185 | let value = value.to_string(); 186 | let value = value.as_str(); 187 | 188 | match value.parse::() { 189 | Ok(v) => { 190 | async_get_retry_interval_ms = v; 191 | } 192 | Err(_) => panic!("Invalid attribute argument value `{}`.", value), 193 | } 194 | } 195 | "async_set_retry_interval_ms" => { 196 | let value = value.to_string(); 197 | let value = value.as_str(); 198 | 199 | match value.parse::() { 200 | Ok(v) => { 201 | async_set_retry_interval_ms = v; 202 | } 203 | Err(_) => panic!("Invalid attribute argument value `{}`.", value), 204 | } 205 | } 206 | "shutdown_retry_interval_ms" => { 207 | let value = value.to_string(); 208 | let value = value.as_str(); 209 | 210 | match value.parse::() { 211 | Ok(v) => { 212 | shutdown_retry_interval_ms = v; 213 | } 214 | Err(_) => panic!("Invalid attribute argument value `{}`.", value), 215 | } 216 | } 217 | "memory" => { 218 | let value = value.to_string(); 219 | let value = value.as_str(); 220 | 221 | match value.parse::() { 222 | Ok(v) => { 223 | memory = v; 224 | } 225 | Err(_) => panic!("Invalid attribute argument value `{}`.", value), 226 | } 227 | } 228 | "fuel" => { 229 | let value = value.to_string(); 230 | let value = value.as_str(); 231 | 232 | match value.parse::() { 233 | Ok(v) => { 234 | fuel = v; 235 | } 236 | Err(_) => panic!("Invalid attribute argument value `{}`.", value), 237 | } 238 | } 239 | "fanout" => { 240 | let value = value.to_string(); 241 | let value = value.as_str(); 242 | 243 | fanout = Literal::from_str(value).expect("The fanout was not a valid string."); 244 | } 245 | _ => panic!("Invalid attribute argument name `{}`.", key), 246 | } 247 | } 248 | 249 | // Get some special quotes. 250 | 251 | let config = quote! { 252 | let mut config = lucidity::lunatic::ProcessConfig::new().unwrap(); 253 | config.set_can_spawn_processes(true); 254 | config.set_can_create_configs(true); 255 | config.set_can_compile_modules(true); 256 | config.set_max_fuel(#fuel); 257 | config.set_max_memory(#memory); 258 | }; 259 | 260 | let service = quote! { 261 | let service = loop { 262 | match #service_name_ident::on_node(node).configure(&config).start_timeout((), std::time::Duration::from_millis(#init_retry_interval_ms)) { 263 | Ok(s) => { 264 | break s; 265 | }, 266 | Err(e) => match e { 267 | lucidity::lunatic::ap::StartupError::TimedOut => continue, 268 | _ => panic!("Init error: {:#?}", e) 269 | } 270 | } 271 | }; 272 | }; 273 | 274 | // Generate the code. 275 | 276 | let gen = quote! { 277 | #input 278 | 279 | /// The generated "local" function. 280 | /// 281 | /// This is a helper function for cases where you want to call the [`lucidity::job`] synchronously 282 | /// on the local node. 283 | /// 284 | /// This function will block the current lunatic process until completion of the service process. 285 | /// 286 | /// The sync method retry interval is defined by `sync_retry_interval_ms` (default 100ms). 287 | /// 288 | /// The shutdown method retry interval is defined by `shutdown_retry_interval_ms` (default 100ms). 289 | /// 290 | /// The service process is `shutdown` before completion of this call. 291 | #vis fn #local_fn_ident(#arguments) -> #return_type { 292 | use lucidity::lunatic::AbstractProcess; 293 | 294 | let node = lucidity::lunatic::host::node_id(); 295 | 296 | #config 297 | 298 | #service 299 | 300 | // Get the result. 301 | let result = loop { 302 | if let Ok(r) = service.with_timeout(std::time::Duration::from_millis(#sync_retry_interval_ms)).#get_ident(#call_arguments) { 303 | break r; 304 | } 305 | }; 306 | 307 | // Shutdown. 308 | loop { 309 | if let Ok(_) = service.with_timeout(std::time::Duration::from_millis(#shutdown_retry_interval_ms)).shutdown() { 310 | break; 311 | } 312 | } 313 | 314 | result 315 | } 316 | 317 | /// The generated "remote" function. 318 | /// 319 | /// This is a helper function for cases where you want to call the [`lucidity::job`] synchronously 320 | /// on a random node in the distributed pool. 321 | /// 322 | /// This function will block the current lunatic process until completion of the service process. 323 | /// 324 | /// The sync method retry interval is defined by `sync_retry_interval_ms` (default 100ms). 325 | /// 326 | /// The shutdown method retry interval is defined by `shutdown_retry_interval_ms` (default 100ms). 327 | /// 328 | /// The service process is `shutdown` before completion of this call. 329 | #vis fn #remote_fn_ident(#arguments) -> #return_type { 330 | use lucidity::lunatic::AbstractProcess; 331 | use lucidity::rand::seq::SliceRandom; 332 | 333 | let nodes = lucidity::lunatic::distributed::nodes(); 334 | let node = *nodes.choose(&mut lucidity::rand::thread_rng()).unwrap(); 335 | 336 | #config 337 | 338 | #service 339 | 340 | // Get the result. 341 | let result = loop { 342 | if let Ok(r) = service.with_timeout(std::time::Duration::from_millis(#sync_retry_interval_ms)).#get_ident(#call_arguments) { 343 | break r; 344 | } 345 | }; 346 | 347 | // Shutdown. 348 | loop { 349 | if let Ok(_) = service.with_timeout(std::time::Duration::from_millis(#shutdown_retry_interval_ms)).shutdown() { 350 | break; 351 | } 352 | } 353 | 354 | result 355 | } 356 | 357 | /// The generated "local async" function. 358 | /// 359 | /// This is a helper function for cases where you want to call the [`lucidity::job`] asynchronously 360 | /// on the local node. 361 | /// 362 | /// This function returns a [`Job`] that can be used to poll, or await, the result of the async process. 363 | /// 364 | /// The async init method retry interval is defined by `async_init_retry_interval_ms` (default 100ms). 365 | /// 366 | /// The spawned service process is `shutdown` when the returned [`Job`] is dropped. 367 | #vis fn #local_async_fn_ident(#arguments) -> #job_name_ident { 368 | use lucidity::lunatic::AbstractProcess; 369 | 370 | let node = lucidity::lunatic::host::node_id(); 371 | 372 | #config 373 | 374 | #service 375 | 376 | // Get the result. 377 | let _ = loop { 378 | if let Ok(r) = service.with_timeout(std::time::Duration::from_millis(#async_init_retry_interval_ms)).#async_init_ident(#call_arguments) { 379 | break r; 380 | } 381 | }; 382 | 383 | #job_name_ident(lucidity::Job { 384 | process: service 385 | }) 386 | } 387 | 388 | /// The generated "remote async" function. 389 | /// 390 | /// This is a helper function for cases where you want to call the [`lucidity::job`] asynchronously 391 | /// on a random node in the distributed pool. 392 | /// 393 | /// This function returns a [`Job`] that can be used to poll, or await, the result of the async process. 394 | /// 395 | /// The async init method retry interval is defined by `async_init_retry_interval_ms` (default 100ms). 396 | /// 397 | /// The spawned service process is `shutdown` when the returned [`Job`] is dropped. 398 | #vis fn #remote_async_fn_ident(#arguments) -> #job_name_ident { 399 | use lucidity::lunatic::AbstractProcess; 400 | use lucidity::rand::seq::SliceRandom; 401 | 402 | let nodes = lucidity::lunatic::distributed::nodes(); 403 | let node = *nodes.choose(&mut lucidity::rand::thread_rng()).unwrap(); 404 | 405 | #config 406 | 407 | #service 408 | 409 | // Get the result. 410 | let _ = loop { 411 | if let Ok(r) = service.with_timeout(std::time::Duration::from_millis(#async_init_retry_interval_ms)).#async_init_ident(#call_arguments) { 412 | break r; 413 | } 414 | }; 415 | 416 | #job_name_ident(lucidity::Job { 417 | process: service 418 | }) 419 | } 420 | 421 | /// The generated "remote async fanout" function. 422 | /// 423 | /// This is a helper function for cases where you want to call the [`lucidity::job`] asynchronously 424 | /// across a distributed set of machines. Essentially, this is like a "rayon fanout", except that 425 | /// it fans across machines, rather than cores. 426 | /// 427 | /// This functionality could be achieved using the other async methods, but this helper makes it easier to 428 | /// fanout, wait, and receive all of the results. 429 | /// 430 | /// The async init method retry interval is defined by `async_init_retry_interval_ms` (default 100ms). 431 | /// 432 | /// The async get method retry interval is defined by `async_get_retry_interval_ms` (default 100ms). 433 | /// 434 | /// The shutdown method retry interval is defined by `shutdown_retry_interval_ms` (default 100ms). 435 | #vis fn #remote_fanout_fn_ident(args_list: Vec<#arguments_types_list>) -> Vec<#return_type> { 436 | use lucidity::lunatic::AbstractProcess; 437 | use lucidity::rand::seq::SliceRandom; 438 | 439 | #config 440 | 441 | let mut services = Vec::new(); 442 | let nodes = lucidity::lunatic::distributed::nodes(); 443 | 444 | let random = &mut lucidity::rand::thread_rng(); 445 | 446 | for (k, args) in args_list.into_iter().enumerate() { 447 | let node = if #fanout == "roundrobin" { 448 | let num_nodes = nodes.len(); 449 | nodes[k % num_nodes] 450 | } else { 451 | // Default to random. 452 | *nodes.choose(random).unwrap() 453 | }; 454 | 455 | #service 456 | 457 | loop { 458 | if let Ok(_) = service.with_timeout(std::time::Duration::from_millis(#async_init_retry_interval_ms)).#async_init_ident(#arguments_args_tuple_list) { 459 | break; 460 | } 461 | } 462 | 463 | services.push(service); 464 | } 465 | 466 | // Get all of the results. 467 | let mut results = vec![None; services.len()]; 468 | loop { 469 | let mut done = true; 470 | 471 | for (k, service) in services.iter_mut().enumerate() { 472 | if results[k].is_some() { 473 | continue; 474 | } 475 | 476 | // Get the result. 477 | let result = loop { 478 | if let Ok(r) = service.with_timeout(std::time::Duration::from_millis(#async_get_retry_interval_ms)).#try_get_ident() { 479 | break r; 480 | } 481 | }; 482 | 483 | if let Some(result) = result { 484 | // Set the result. 485 | results[k] = Some(result); 486 | 487 | // Shutdown. 488 | loop { 489 | if let Ok(_) = service.with_timeout(std::time::Duration::from_millis(#shutdown_retry_interval_ms)).shutdown() { 490 | break; 491 | } 492 | } 493 | } else { 494 | done = false; 495 | } 496 | } 497 | 498 | if done { 499 | break; 500 | } 501 | 502 | // Give some time for the processes to respond between loops. 503 | lucidity::lunatic::sleep(std::time::Duration::from_millis(#async_get_retry_interval_ms)); 504 | } 505 | 506 | results.into_iter().map(|result| result.unwrap()).collect() 507 | } 508 | 509 | /// The generated [`AbstractProcess`] for the [`lucidity::job`]. 510 | /// 511 | /// This defines the proper methods to achieve synchronous, and asynchronous calls to a process 512 | /// that may be local or remote. All of the generated functions make calls into this processes 513 | /// request handlers. 514 | #vis struct #service_name_ident(#option_return_type); 515 | 516 | #[lucidity::lunatic::abstract_process(serializer = lucidity::lunatic::serializer::Bincode)] 517 | impl #service_name_ident { 518 | #[init] 519 | fn init(_: lucidity::lunatic::ap::Config, _: ()) -> Result { 520 | Ok(Self(None)) 521 | } 522 | 523 | #[terminate] 524 | fn terminate(self) { 525 | 526 | } 527 | 528 | #[handle_link_death] 529 | fn handle_link_death(&self, _tag: lucidity::lunatic::Tag) { 530 | } 531 | 532 | #[handle_request] 533 | fn #get_ident(&self, #arguments) -> #return_type { 534 | #name(#call_arguments) 535 | } 536 | 537 | #[handle_request] 538 | fn #set_ident(&mut self, value: #return_type) { 539 | self.0 = Some(value); 540 | } 541 | 542 | #[handle_request] 543 | fn #try_get_ident(&self) -> #option_return_type { 544 | self.0.clone() 545 | } 546 | 547 | #[handle_request] 548 | fn #async_init_ident(&self, #arguments) { 549 | let parent: lucidity::lunatic::ap::ProcessRef<#service_name_ident> = unsafe { lucidity::lunatic::ap::ProcessRef::new(lucidity::lunatic::host::node_id(), lucidity::lunatic::host::process_id()) }; 550 | 551 | #config 552 | 553 | let _ = lucidity::lunatic::Process::spawn_link_config(&config, (parent, #call_arguments), |(parent, #closure_arguments), _: lucidity::lunatic::Mailbox<()>| { 554 | 555 | let result = #name(#call_arguments); 556 | 557 | loop { 558 | if let Ok(_) = parent.with_timeout(std::time::Duration::from_millis(#async_set_retry_interval_ms)).#set_ident(result.clone()) { 559 | break; 560 | } 561 | } 562 | }); 563 | } 564 | } 565 | 566 | /// The [`Job`] type for the generated service. 567 | /// 568 | /// This type is usually created with the [`lucidity::job`] macro on the async methods. 569 | /// The async methods are [`#local_async_fn_ident`] and [`#remote_async_fn_ident`], 570 | /// and they return this type, so that `try_get` and `await_get` can be called on it. 571 | /// 572 | /// When this type is dropped, the underlying process is shutdown. 573 | #vis struct #job_name_ident(#vis lucidity::Job<#service_name_ident>); 574 | 575 | impl #job_name_ident { 576 | /// The `try_get` method on the generated [`Job`] type calls the service process to check if a value is ready. 577 | /// 578 | /// This is generally used in some sort of loop, or context where multiple values need to be checked repeatedly. 579 | /// As the underlying lunatic runtime uses a message-based coroutine paradigm, this method acts as a helper to 580 | /// synchronize across processes. 581 | #vis fn try_get(&self) -> #option_return_type { 582 | loop { 583 | if let Ok(r) = self.0.process.with_timeout(std::time::Duration::from_millis(#async_get_retry_interval_ms)).#try_get_ident() { 584 | return r; 585 | } 586 | } 587 | } 588 | 589 | /// The `await_get` method on the generated [`Job`] type calls the service process repeatedly to check if a value is ready. 590 | /// 591 | /// This is achieved by looping over `try_get` with a timeout of `async_get_retry_interval_ms` (default 100ms). 592 | /// As the underlying lunatic runtime uses a message-based coroutine paradigm, this method acts as a helper to 593 | /// synchronize across processes. 594 | #vis fn await_get(&self) -> #return_type { 595 | loop { 596 | if let Ok(r) = self.0.process.with_timeout(std::time::Duration::from_millis(#async_get_retry_interval_ms)).#try_get_ident() { 597 | if let Some(r) = r { 598 | return r; 599 | } else { 600 | lucidity::lunatic::sleep(std::time::Duration::from_millis(#async_get_retry_interval_ms)); 601 | } 602 | } 603 | } 604 | } 605 | } 606 | }; 607 | 608 | gen 609 | } 610 | 611 | // Tests. 612 | 613 | #[cfg(test)] 614 | mod tests { 615 | use super::*; 616 | //use pretty_assertions::assert_eq; 617 | 618 | #[test] 619 | fn test_job() { 620 | let input = quote! { 621 | fn pythagorean(num1: u32, num2: u32) -> f32 { 622 | ((num1 * num1 + num2 * num2) as f32).sqrt() 623 | } 624 | }; 625 | 626 | let _ = job_inner(TokenStream::new(), input).to_string(); 627 | } 628 | } 629 | -------------------------------------------------------------------------------- /lucidity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lucidity" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Aaron Roney "] 6 | license = "MIT" 7 | description = "A distributed orchestrator platform for Rust." 8 | readme = "../README.md" 9 | homepage = "https://github.com/twitchax/lucidity" 10 | repository = "https://github.com/twitchax/lucidity" 11 | keywords = ["lucidity", "orchestrator", "distributed", "macro"] 12 | categories = ["orchestration"] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [features] 17 | default = [] 18 | fly = [] 19 | 20 | [dependencies] 21 | 22 | lucidity-core = { path = "../lucidity-core", version = "0.1.0" } 23 | lucidity-macros = { path = "../lucidity-macros", version = "0.1.0" } 24 | 25 | nightfly = { version = "0.1.6" } 26 | 27 | serde_json = "1.0.110" -------------------------------------------------------------------------------- /lucidity/src/fly.rs: -------------------------------------------------------------------------------- 1 | //! The module for automatically building a `lunatic` cluster on fly.io. 2 | 3 | use std::time::Duration; 4 | 5 | use lucidity_core::lunatic::{ 6 | protocol::{Protocol, Send, TaskEnd}, 7 | Process, 8 | }; 9 | use serde_json::{json, Value}; 10 | 11 | const ENDPOINT: &str = "https://api.machines.dev/v1"; 12 | 13 | /// Ensures that the `lunatic` cluster is running on fly.io. 14 | /// 15 | /// This function takes the app name, region, and number of machines to run. 16 | /// It will delete any existing machines, create new ones, prepare them, 17 | /// and then run the `lunatic` process on them, connecting the node to the 18 | /// machine id that called this function. 19 | pub fn ensure_machines(key: &str, count: usize) -> Result<(), String> { 20 | let app_name = std::env::var("FLY_APP_NAME").expect("FLY_APP_NAME not set."); 21 | let local_machine_id = std::env::var("FLY_MACHINE_ID").expect("FLY_MACHINE_ID not set."); 22 | let region = std::env::var("FLY_REGION").expect("FLY_REGION not set."); 23 | 24 | // Create all of the processes. 25 | let mut processes = Vec::new(); 26 | for k in 1..=count { 27 | let machine_name = format!("lucid-{}", k); 28 | let p = Process::spawn_link( 29 | (key.to_owned(), app_name.to_owned(), machine_name, region.to_owned(), local_machine_id.to_owned()), 30 | |(key, app_name, machine_name, region, local_machine_id), m: Protocol, TaskEnd>>| match ensure_machine(&key, &app_name, &machine_name, ®ion, &local_machine_id) { 31 | Ok(o) => { 32 | let _ = m.send(Ok(o)); 33 | } 34 | Err(e) => { 35 | let _ = m.send(Err(e)); 36 | } 37 | }, 38 | ); 39 | 40 | processes.push(p); 41 | } 42 | 43 | // Wait for all of the processes to finish. 44 | let mut results = Vec::new(); 45 | for p in processes { 46 | results.push(p.result()); 47 | } 48 | 49 | // Check for any errors. 50 | for r in results { 51 | match r { 52 | Ok(_) => {} 53 | Err(e) => { 54 | return Err(e); 55 | } 56 | } 57 | } 58 | 59 | Ok(()) 60 | } 61 | 62 | fn ensure_machine(key: &str, app_name: &str, machine_name: &str, region: &str, local_machine_id: &str) -> Result<(), String> { 63 | // Delete any existing machine. 64 | delete_machine(key, app_name, machine_name); 65 | 66 | // Wait for the machine to be deleted. 67 | // TODO: Use events instead of sleeping. 68 | crate::lunatic::sleep(Duration::from_secs(30)); 69 | 70 | // Create a new machine. 71 | create_machine(key, app_name, machine_name, region, local_machine_id).map_err(|e| format!("[ensure_machine] Failed to create machine. {}", e))?; 72 | 73 | // Wait for the machine to be ready. 74 | // TODO: Use events instead of sleeping. 75 | crate::lunatic::sleep(Duration::from_secs(30)); 76 | 77 | Ok(()) 78 | } 79 | 80 | /// List the currently running machines for the app. 81 | pub fn list_machines(key: &str, app_name: &str) -> Result, String> { 82 | let client = nightfly::Client::new(); 83 | 84 | let response = client 85 | .get(format!("{}/apps/{}/machines", ENDPOINT, app_name)) 86 | .bearer_auth(key) 87 | .send() 88 | .map_err(|e| format!("[list_machines] Failed to send request. {}", e))?; 89 | 90 | let value = response.json::>().map_err(|e| format!("[list_machines] Failed to parse response. {}", e))?; 91 | 92 | Ok(value) 93 | } 94 | 95 | fn machine_id_from_name(key: &str, app_name: &str, machine_name: &str) -> Result { 96 | let machines = list_machines(key, app_name).map_err(|e| format!("[machine_id_from_name] Failed to list machines. {}", e))?; 97 | 98 | for machine in machines { 99 | if machine["name"].as_str().unwrap() == machine_name { 100 | return Ok(machine["id"].as_str().unwrap().to_string()); 101 | } 102 | } 103 | 104 | Err(format!("[machine_id_from_name] Machine with name {} not found.", machine_name)) 105 | } 106 | 107 | fn create_machine(key: &str, app_name: &str, machine_name: &str, region: &str, local_machine_id: &str) -> Result<(), String> { 108 | let client = nightfly::Client::new(); 109 | 110 | let body = json!({ 111 | "name": machine_name, 112 | "region": region, 113 | "config": { 114 | "init": { 115 | "exec": [ 116 | "/app.entrypoint.sh", 117 | format!("http://{}.vm.{}.internal:3030/", local_machine_id, app_name) 118 | ] 119 | }, 120 | "image": "twitchax/lunatic:2024.01.10", 121 | "auto_destroy": true, 122 | "restart": { 123 | "policy": "always" 124 | }, 125 | "guest": { 126 | "cpu_kind": "shared", 127 | "cpus": 1, 128 | "memory_mb": 1024 129 | }, 130 | "services": [ 131 | { 132 | "protocol": "udp", 133 | "internal_port": 3031 134 | }, 135 | ], 136 | } 137 | }); 138 | 139 | let _ = client 140 | .post(format!("{}/apps/{}/machines", ENDPOINT, app_name)) 141 | .bearer_auth(key) 142 | .json(body) 143 | .send() 144 | .map_err(|e| format!("[create_machine] Failed to send request. {}", e))?; 145 | 146 | Ok(()) 147 | } 148 | 149 | fn delete_machine(key: &str, app_name: &str, machine_name: &str) { 150 | let machine_id = match machine_id_from_name(key, app_name, machine_name) { 151 | Ok(o) => o, 152 | Err(_) => return, 153 | }; 154 | 155 | let client = nightfly::Client::new(); 156 | 157 | // Swallow errors, as it is possible that the machine doesn't exist. 158 | let _ = client.post(format!("{}/apps/{}/machines/{}/stop", ENDPOINT, app_name, machine_id)).bearer_auth(key).send(); 159 | 160 | // Swallow errors, as it is possible that the machine doesn't exist. 161 | let _ = client.delete(format!("{}/apps/{}/machines/{}", ENDPOINT, app_name, machine_id)).bearer_auth(key).send(); 162 | } 163 | -------------------------------------------------------------------------------- /lucidity/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The `lucidity` crate. Re-exports the core components and provides a macro for creating jobs. 2 | //#![doc = include_str!("../../README.md")] 3 | #![warn(rustdoc::broken_intra_doc_links, rust_2018_idioms, clippy::all, missing_docs)] 4 | 5 | pub use lucidity_core::Job; 6 | pub use lucidity_macros::job; 7 | 8 | pub use lucidity_core::lunatic; 9 | pub use lucidity_core::rand; 10 | pub use lucidity_core::serde; 11 | 12 | pub use lucidity_core::lunatic::abstract_process; 13 | 14 | #[cfg(feature = "fly")] 15 | pub mod fly; 16 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2023-12-10" -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 200 2 | struct_lit_width = 40 3 | reorder_impl_items = true 4 | format_macro_bodies = false 5 | format_code_in_doc_comments = true 6 | --------------------------------------------------------------------------------