├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── README.md ├── book.toml ├── simple-rt ├── Cargo.lock ├── Cargo.toml ├── examples │ └── echo.rs └── src │ ├── block_on.rs │ ├── executor.rs │ ├── io.rs │ ├── lib.rs │ └── reactor.rs ├── src ├── Executor.md ├── Future.md ├── Future的例子.md ├── Future的接口.md ├── Introduction.md ├── Reactor.md ├── Reactor的设计.md ├── Rust异步基础部件.md ├── SUMMARY.md ├── Task.md ├── block_on.md ├── images │ ├── reactor.png │ └── relation.png ├── 一个完整的Executor例子.md ├── 异步IO与Poller.md └── 结论.md └── theme ├── book.js ├── css ├── chrome.css ├── general.css ├── print.css └── variables.css ├── favicon.png ├── favicon.svg ├── fonts ├── OPEN-SANS-LICENSE.txt ├── SOURCE-CODE-PRO-LICENSE.txt ├── fonts.css ├── open-sans-v17-all-charsets-300.woff2 ├── open-sans-v17-all-charsets-300italic.woff2 ├── open-sans-v17-all-charsets-600.woff2 ├── open-sans-v17-all-charsets-600italic.woff2 ├── open-sans-v17-all-charsets-700.woff2 ├── open-sans-v17-all-charsets-700italic.woff2 ├── open-sans-v17-all-charsets-800.woff2 ├── open-sans-v17-all-charsets-800italic.woff2 ├── open-sans-v17-all-charsets-italic.woff2 ├── open-sans-v17-all-charsets-regular.woff2 └── source-code-pro-v11-all-charsets-500.woff2 ├── highlight.css ├── highlight.js └── index.hbs /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | with: 13 | fetch-depth: 0 14 | - name: Install mdbook 15 | run: | 16 | mkdir mdbook 17 | curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.27/mdbook-v0.4.27-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook 18 | echo `pwd`/mdbook >> $GITHUB_PATH 19 | - name: Deploy GitHub Pages 20 | run: | 21 | # This assumes your book is in the root of your repository. 22 | # Just add a `cd` here if you need to change to another directory. 23 | mdbook build 24 | git worktree add gh-pages 25 | git config user.name "Deploy from CI" 26 | git config user.email "" 27 | cd gh-pages 28 | # Delete the ref to avoid keeping history. 29 | git update-ref -d refs/heads/gh-pages 30 | rm -rf * 31 | mv ../book/* . 32 | git add . 33 | git commit -m "Deploy $GITHUB_SHA to gh-pages" 34 | git push --force --set-upstream origin gh-pages 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | simple-rt/target -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust异步运行时基础部件 2 | 3 | 目前Rust使用最广的异步运行时是tokio,但tokio是一个十分完整的运行时,对于使用者来说几乎是一个黑盒,除非直接深入其源码,否则无法了解其结构与设计。这又使得tokio又像是一个无缝的蛋,当我们希望定制一些调度规则时,几乎是无从下手。 4 | 5 | 而smol作为tokio的“竞争对手”,其接口设计则好多了,将异步运行时拆分成相对独立的且小巧的几块(`async-io`, `async-executor`, `async-task`等等),能学习者能更好地了解异步运行时的结构,也能让使用者方便定制一些特定的规则。 6 | 7 | 那么这篇文章,大概顺着smol给出的设计思路,从头实现一个简单但齐全的异步运行时,让大家对异步运行时有一个基础的理解。 8 | 9 | 本文代码参考[这里](https://github.com/TOETOE55/async-rt-book/tree/master/simple-rt) 10 | 11 | (一直想写这篇文章,但耽搁了好久) -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["TOETOE55"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Rust异步运行时基础部件" 7 | -------------------------------------------------------------------------------- /simple-rt/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 = "async-task" 7 | version = "4.7.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.2.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.3.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 22 | 23 | [[package]] 24 | name = "bitflags" 25 | version = "2.5.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 28 | 29 | [[package]] 30 | name = "bumpalo" 31 | version = "3.16.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 34 | 35 | [[package]] 36 | name = "cfg-if" 37 | version = "1.0.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 40 | 41 | [[package]] 42 | name = "concurrent-queue" 43 | version = "2.4.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" 46 | dependencies = [ 47 | "crossbeam-utils", 48 | ] 49 | 50 | [[package]] 51 | name = "crossbeam-utils" 52 | version = "0.8.19" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" 55 | 56 | [[package]] 57 | name = "errno" 58 | version = "0.3.8" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 61 | dependencies = [ 62 | "libc", 63 | "windows-sys", 64 | ] 65 | 66 | [[package]] 67 | name = "flume" 68 | version = "0.11.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" 71 | dependencies = [ 72 | "futures-core", 73 | "futures-sink", 74 | "nanorand", 75 | "spin", 76 | ] 77 | 78 | [[package]] 79 | name = "futures" 80 | version = "0.3.30" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 83 | dependencies = [ 84 | "futures-channel", 85 | "futures-core", 86 | "futures-executor", 87 | "futures-io", 88 | "futures-sink", 89 | "futures-task", 90 | "futures-util", 91 | ] 92 | 93 | [[package]] 94 | name = "futures-channel" 95 | version = "0.3.30" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 98 | dependencies = [ 99 | "futures-core", 100 | "futures-sink", 101 | ] 102 | 103 | [[package]] 104 | name = "futures-core" 105 | version = "0.3.30" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 108 | 109 | [[package]] 110 | name = "futures-executor" 111 | version = "0.3.30" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 114 | dependencies = [ 115 | "futures-core", 116 | "futures-task", 117 | "futures-util", 118 | ] 119 | 120 | [[package]] 121 | name = "futures-io" 122 | version = "0.3.30" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 125 | 126 | [[package]] 127 | name = "futures-macro" 128 | version = "0.3.30" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 131 | dependencies = [ 132 | "proc-macro2", 133 | "quote", 134 | "syn", 135 | ] 136 | 137 | [[package]] 138 | name = "futures-sink" 139 | version = "0.3.30" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 142 | 143 | [[package]] 144 | name = "futures-task" 145 | version = "0.3.30" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 148 | 149 | [[package]] 150 | name = "futures-util" 151 | version = "0.3.30" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 154 | dependencies = [ 155 | "futures-channel", 156 | "futures-core", 157 | "futures-io", 158 | "futures-macro", 159 | "futures-sink", 160 | "futures-task", 161 | "memchr", 162 | "pin-project-lite", 163 | "pin-utils", 164 | "slab", 165 | ] 166 | 167 | [[package]] 168 | name = "getrandom" 169 | version = "0.2.14" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" 172 | dependencies = [ 173 | "cfg-if", 174 | "js-sys", 175 | "libc", 176 | "wasi", 177 | "wasm-bindgen", 178 | ] 179 | 180 | [[package]] 181 | name = "hermit-abi" 182 | version = "0.3.9" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 185 | 186 | [[package]] 187 | name = "js-sys" 188 | version = "0.3.69" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 191 | dependencies = [ 192 | "wasm-bindgen", 193 | ] 194 | 195 | [[package]] 196 | name = "libc" 197 | version = "0.2.153" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 200 | 201 | [[package]] 202 | name = "linux-raw-sys" 203 | version = "0.4.13" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" 206 | 207 | [[package]] 208 | name = "lock_api" 209 | version = "0.4.11" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 212 | dependencies = [ 213 | "autocfg", 214 | "scopeguard", 215 | ] 216 | 217 | [[package]] 218 | name = "log" 219 | version = "0.4.21" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 222 | 223 | [[package]] 224 | name = "memchr" 225 | version = "2.7.2" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 228 | 229 | [[package]] 230 | name = "nanorand" 231 | version = "0.7.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" 234 | dependencies = [ 235 | "getrandom", 236 | ] 237 | 238 | [[package]] 239 | name = "once_cell" 240 | version = "1.19.0" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 243 | 244 | [[package]] 245 | name = "parking" 246 | version = "2.2.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" 249 | 250 | [[package]] 251 | name = "parking_lot" 252 | version = "0.12.1" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 255 | dependencies = [ 256 | "lock_api", 257 | "parking_lot_core", 258 | ] 259 | 260 | [[package]] 261 | name = "parking_lot_core" 262 | version = "0.9.9" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 265 | dependencies = [ 266 | "cfg-if", 267 | "libc", 268 | "redox_syscall", 269 | "smallvec", 270 | "windows-targets 0.48.5", 271 | ] 272 | 273 | [[package]] 274 | name = "pin-project-lite" 275 | version = "0.2.14" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 278 | 279 | [[package]] 280 | name = "pin-utils" 281 | version = "0.1.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 284 | 285 | [[package]] 286 | name = "polling" 287 | version = "3.6.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" 290 | dependencies = [ 291 | "cfg-if", 292 | "concurrent-queue", 293 | "hermit-abi", 294 | "pin-project-lite", 295 | "rustix", 296 | "tracing", 297 | "windows-sys", 298 | ] 299 | 300 | [[package]] 301 | name = "proc-macro2" 302 | version = "1.0.79" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 305 | dependencies = [ 306 | "unicode-ident", 307 | ] 308 | 309 | [[package]] 310 | name = "quote" 311 | version = "1.0.35" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 314 | dependencies = [ 315 | "proc-macro2", 316 | ] 317 | 318 | [[package]] 319 | name = "redox_syscall" 320 | version = "0.4.1" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 323 | dependencies = [ 324 | "bitflags 1.3.2", 325 | ] 326 | 327 | [[package]] 328 | name = "rustix" 329 | version = "0.38.32" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" 332 | dependencies = [ 333 | "bitflags 2.5.0", 334 | "errno", 335 | "libc", 336 | "linux-raw-sys", 337 | "windows-sys", 338 | ] 339 | 340 | [[package]] 341 | name = "scopeguard" 342 | version = "1.2.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 345 | 346 | [[package]] 347 | name = "simple-rt" 348 | version = "0.1.0" 349 | dependencies = [ 350 | "async-task", 351 | "flume", 352 | "futures", 353 | "parking", 354 | "parking_lot", 355 | "polling", 356 | "rustix", 357 | "slab", 358 | "waker-fn", 359 | ] 360 | 361 | [[package]] 362 | name = "slab" 363 | version = "0.4.9" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 366 | dependencies = [ 367 | "autocfg", 368 | ] 369 | 370 | [[package]] 371 | name = "smallvec" 372 | version = "1.13.2" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 375 | 376 | [[package]] 377 | name = "spin" 378 | version = "0.9.8" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 381 | dependencies = [ 382 | "lock_api", 383 | ] 384 | 385 | [[package]] 386 | name = "syn" 387 | version = "2.0.58" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" 390 | dependencies = [ 391 | "proc-macro2", 392 | "quote", 393 | "unicode-ident", 394 | ] 395 | 396 | [[package]] 397 | name = "tracing" 398 | version = "0.1.40" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 401 | dependencies = [ 402 | "pin-project-lite", 403 | "tracing-core", 404 | ] 405 | 406 | [[package]] 407 | name = "tracing-core" 408 | version = "0.1.32" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 411 | 412 | [[package]] 413 | name = "unicode-ident" 414 | version = "1.0.12" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 417 | 418 | [[package]] 419 | name = "waker-fn" 420 | version = "1.1.1" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" 423 | 424 | [[package]] 425 | name = "wasi" 426 | version = "0.11.0+wasi-snapshot-preview1" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 429 | 430 | [[package]] 431 | name = "wasm-bindgen" 432 | version = "0.2.92" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 435 | dependencies = [ 436 | "cfg-if", 437 | "wasm-bindgen-macro", 438 | ] 439 | 440 | [[package]] 441 | name = "wasm-bindgen-backend" 442 | version = "0.2.92" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 445 | dependencies = [ 446 | "bumpalo", 447 | "log", 448 | "once_cell", 449 | "proc-macro2", 450 | "quote", 451 | "syn", 452 | "wasm-bindgen-shared", 453 | ] 454 | 455 | [[package]] 456 | name = "wasm-bindgen-macro" 457 | version = "0.2.92" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 460 | dependencies = [ 461 | "quote", 462 | "wasm-bindgen-macro-support", 463 | ] 464 | 465 | [[package]] 466 | name = "wasm-bindgen-macro-support" 467 | version = "0.2.92" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 470 | dependencies = [ 471 | "proc-macro2", 472 | "quote", 473 | "syn", 474 | "wasm-bindgen-backend", 475 | "wasm-bindgen-shared", 476 | ] 477 | 478 | [[package]] 479 | name = "wasm-bindgen-shared" 480 | version = "0.2.92" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 483 | 484 | [[package]] 485 | name = "windows-sys" 486 | version = "0.52.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 489 | dependencies = [ 490 | "windows-targets 0.52.4", 491 | ] 492 | 493 | [[package]] 494 | name = "windows-targets" 495 | version = "0.48.5" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 498 | dependencies = [ 499 | "windows_aarch64_gnullvm 0.48.5", 500 | "windows_aarch64_msvc 0.48.5", 501 | "windows_i686_gnu 0.48.5", 502 | "windows_i686_msvc 0.48.5", 503 | "windows_x86_64_gnu 0.48.5", 504 | "windows_x86_64_gnullvm 0.48.5", 505 | "windows_x86_64_msvc 0.48.5", 506 | ] 507 | 508 | [[package]] 509 | name = "windows-targets" 510 | version = "0.52.4" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" 513 | dependencies = [ 514 | "windows_aarch64_gnullvm 0.52.4", 515 | "windows_aarch64_msvc 0.52.4", 516 | "windows_i686_gnu 0.52.4", 517 | "windows_i686_msvc 0.52.4", 518 | "windows_x86_64_gnu 0.52.4", 519 | "windows_x86_64_gnullvm 0.52.4", 520 | "windows_x86_64_msvc 0.52.4", 521 | ] 522 | 523 | [[package]] 524 | name = "windows_aarch64_gnullvm" 525 | version = "0.48.5" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 528 | 529 | [[package]] 530 | name = "windows_aarch64_gnullvm" 531 | version = "0.52.4" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" 534 | 535 | [[package]] 536 | name = "windows_aarch64_msvc" 537 | version = "0.48.5" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 540 | 541 | [[package]] 542 | name = "windows_aarch64_msvc" 543 | version = "0.52.4" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" 546 | 547 | [[package]] 548 | name = "windows_i686_gnu" 549 | version = "0.48.5" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 552 | 553 | [[package]] 554 | name = "windows_i686_gnu" 555 | version = "0.52.4" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" 558 | 559 | [[package]] 560 | name = "windows_i686_msvc" 561 | version = "0.48.5" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 564 | 565 | [[package]] 566 | name = "windows_i686_msvc" 567 | version = "0.52.4" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" 570 | 571 | [[package]] 572 | name = "windows_x86_64_gnu" 573 | version = "0.48.5" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 576 | 577 | [[package]] 578 | name = "windows_x86_64_gnu" 579 | version = "0.52.4" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" 582 | 583 | [[package]] 584 | name = "windows_x86_64_gnullvm" 585 | version = "0.48.5" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 588 | 589 | [[package]] 590 | name = "windows_x86_64_gnullvm" 591 | version = "0.52.4" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" 594 | 595 | [[package]] 596 | name = "windows_x86_64_msvc" 597 | version = "0.48.5" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 600 | 601 | [[package]] 602 | name = "windows_x86_64_msvc" 603 | version = "0.52.4" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" 606 | -------------------------------------------------------------------------------- /simple-rt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simple-rt" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | async-task = "4.7.0" 10 | flume = "0.11.0" 11 | futures = "0.3.30" 12 | parking = "2.2.0" 13 | parking_lot = "0.12.1" 14 | polling = "3.6.0" 15 | rustix = "0.38.32" 16 | slab = "0.4.9" 17 | waker-fn = "1.1.1" 18 | -------------------------------------------------------------------------------- /simple-rt/examples/echo.rs: -------------------------------------------------------------------------------- 1 | use std::{io, task::Poll, thread}; 2 | 3 | use futures::future::poll_fn; 4 | 5 | use simple_rt::{block_on, Executor, Reactor, Stdin}; 6 | 7 | async fn yield_now() { 8 | let mut x = false; 9 | poll_fn(|cx| { 10 | if x { 11 | Poll::Ready(()) 12 | } else { 13 | x = true; 14 | cx.waker().wake_by_ref(); 15 | Poll::Pending 16 | } 17 | }) 18 | .await; 19 | } 20 | 21 | fn main() -> io::Result<()> { 22 | let reactor = Reactor::new()?; 23 | let ex = Executor::new(); 24 | 25 | thread::scope(|s| { 26 | // reactor io线程,用于处理IO事件 27 | s.spawn(|| reactor.event_loop().unwrap()); 28 | 29 | // 起8个线程作为线程池,来并发执行task 30 | for _ in 0..8 { 31 | s.spawn(|| { 32 | block_on(ex.execute()); 33 | }); 34 | } 35 | 36 | // 创建异步任务,丢到Executor中执行 37 | ex.spawn(async { 38 | let mut buf = [0; 1000]; 39 | let mut buf = &mut buf[..]; 40 | let stdin = Stdin::new(&reactor).unwrap(); 41 | 42 | while buf.len() > 0 { 43 | let x = stdin.read(buf).await.unwrap(); 44 | println!("from stdin: {:?}", String::from_utf8_lossy(&buf[..x])); 45 | 46 | buf = &mut buf[x..]; 47 | } 48 | }) 49 | .detach(); 50 | 51 | // 这个也会丢到Executor,然后被并发执行 52 | ex.spawn(async { 53 | yield_now().await; 54 | println!("yield 1"); 55 | yield_now().await; 56 | println!("yield 2"); 57 | }) 58 | .detach(); 59 | }); 60 | 61 | Ok(()) 62 | } 63 | -------------------------------------------------------------------------------- /simple-rt/src/block_on.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::{Future, IntoFuture}, 3 | pin::pin, 4 | task::{Context, Poll}, 5 | }; 6 | use waker_fn::waker_fn; 7 | 8 | pub fn block_on(fut: impl IntoFuture) -> T { 9 | // 当前线程的`parker`和`unparker` 10 | let (parker, unparker) = parking::pair(); 11 | // waker在调用`.wake()`时unpark当前线程 12 | let waker = waker_fn(move || { 13 | unparker.unpark(); 14 | }); 15 | let cx = &mut Context::from_waker(&waker); 16 | 17 | // 轮询`Future` 18 | let mut fut = pin!(fut.into_future()); 19 | loop { 20 | if let Poll::Ready(t) = fut.as_mut().poll(cx) { 21 | return t; 22 | } 23 | 24 | // 返回`Pending`就休眠线程,等待`waker`被调用 25 | // 注:如果waker已经被调用过了,这里就不会阻塞。 26 | parker.park() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /simple-rt/src/executor.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use async_task::{Runnable, Task}; 4 | use futures::StreamExt; 5 | use std::future::Future; 6 | 7 | pub struct Executor<'a> { 8 | tx: flume::Sender, 9 | rx: flume::Receiver, 10 | 11 | /// Makes the `'a` lifetime invariant. 12 | _marker: PhantomData>, 13 | } 14 | 15 | impl<'a> Executor<'a> { 16 | pub fn new() -> Self { 17 | let (tx, rx) = flume::unbounded(); 18 | 19 | Self { 20 | tx, 21 | rx, 22 | _marker: PhantomData, 23 | } 24 | } 25 | 26 | // 创建一个Task,并丢到Executor中 27 | pub fn spawn(&self, future: impl Future + Send + 'a) -> Task { 28 | let tx = self.tx.clone(); 29 | let schedule = move |runnable| tx.send(runnable).unwrap(); 30 | 31 | // 创建一个task,并直接丢到队列里 32 | let (runnable, task) = unsafe { async_task::spawn_unchecked(future, schedule) }; 33 | runnable.schedule(); 34 | 35 | task 36 | } 37 | 38 | // 当executor里有新的task时,就会拿出来执行 39 | pub async fn execute(&self) { 40 | let mut rx = self.rx.stream(); 41 | while let Some(runnable) = rx.next().await { 42 | runnable.run(); 43 | } 44 | } 45 | } 46 | 47 | impl Default for Executor<'_> { 48 | fn default() -> Self { 49 | Self::new() 50 | } 51 | } 52 | 53 | unsafe impl Send for Executor<'_> {} 54 | unsafe impl Sync for Executor<'_> {} 55 | -------------------------------------------------------------------------------- /simple-rt/src/io.rs: -------------------------------------------------------------------------------- 1 | use crate::reactor::Reactor; 2 | use std::{ 3 | io::{self, Read}, 4 | os::fd::AsFd, 5 | }; 6 | 7 | pub struct Stdin<'r> { 8 | reactor: &'r Reactor, 9 | stdin: io::Stdin, 10 | } 11 | 12 | impl<'r> Stdin<'r> { 13 | pub fn new(reactor: &'r Reactor) -> io::Result { 14 | let this = Self { 15 | reactor, 16 | stdin: io::stdin(), 17 | }; 18 | 19 | rustix::io::ioctl_fionbio(&this.stdin, true)?; 20 | 21 | Ok(this) 22 | } 23 | 24 | pub async fn read(&self, buf: &mut [u8]) -> io::Result { 25 | loop { 26 | match self.stdin.lock().read(buf) { 27 | Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} 28 | res => return res, 29 | } 30 | self.reactor.register_readable(self.stdin.as_fd()).await?; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /simple-rt/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate core; 2 | 3 | pub mod block_on; 4 | pub mod executor; 5 | pub mod io; 6 | pub mod reactor; 7 | 8 | pub use block_on::block_on; 9 | pub use executor::Executor; 10 | pub use io::Stdin; 11 | pub use reactor::Reactor; 12 | -------------------------------------------------------------------------------- /simple-rt/src/reactor.rs: -------------------------------------------------------------------------------- 1 | use futures::task::AtomicWaker; 2 | use futures::Future; 3 | use parking_lot::Mutex; 4 | use polling::{Event, Events, Poller}; 5 | use slab::Slab; 6 | use std::future::poll_fn; 7 | use std::io::{self, ErrorKind}; 8 | use std::os::fd::RawFd; 9 | use std::os::fd::{AsRawFd, BorrowedFd}; 10 | use std::pin::Pin; 11 | use std::sync::atomic::{AtomicBool, Ordering}; 12 | use std::sync::Arc; 13 | use std::task::{Context, Poll, Waker}; 14 | use waker_fn::waker_fn; 15 | 16 | pub struct Reactor { 17 | poller: Arc, 18 | repo: Mutex>>, 19 | } 20 | 21 | struct IOEvent { 22 | fd: RawFd, 23 | key: usize, 24 | is_ready: AtomicBool, 25 | waker: AtomicWaker, 26 | } 27 | 28 | impl Reactor { 29 | pub fn new() -> io::Result { 30 | Ok(Self { 31 | poller: Arc::new(Poller::new()?), 32 | repo: Mutex::new(Slab::new()), 33 | }) 34 | } 35 | 36 | // 阻塞至关心的fd就绪 37 | pub fn block_until(&self) -> io::Result<()> { 38 | let mut events = Events::new(); 39 | 40 | match self.poller.wait(&mut events, None) { 41 | Ok(0) => Ok(()), 42 | Ok(_) => { 43 | let repo = self.repo.lock(); 44 | for ev in events.iter() { 45 | if let Some(s) = repo.get(ev.key) { 46 | let _ = s.waker.take().map(Waker::wake); 47 | s.is_ready.swap(true, Ordering::Release); 48 | } 49 | } 50 | Ok(()) 51 | } 52 | Err(err) if err.kind() == ErrorKind::Interrupted => Ok(()), 53 | Err(err) => Err(err), 54 | } 55 | } 56 | 57 | // 唤醒bloking_until 58 | pub fn waker(&self) -> Waker { 59 | let poller = self.poller.clone(); 60 | waker_fn(move || { 61 | let _ = poller.notify(); 62 | }) 63 | } 64 | 65 | pub fn event_loop(&self) -> io::Result<()> { 66 | loop { 67 | self.block_until()?; 68 | } 69 | } 70 | 71 | // 注册可读fd,直到fd就绪 72 | pub async fn register_readable(&self, fd: BorrowedFd<'_>) -> io::Result<()> { 73 | struct IoGuard<'r> { 74 | reactor: &'r Reactor, 75 | event: Arc, 76 | } 77 | 78 | impl<'r> IoGuard<'r> { 79 | fn new(reactor: &'r Reactor, fd: BorrowedFd<'_>) -> io::Result { 80 | let event = { 81 | let mut events = reactor.repo.lock(); 82 | let entry = events.vacant_entry(); 83 | let event = Arc::new(IOEvent { 84 | fd: fd.as_raw_fd(), 85 | key: entry.key(), 86 | is_ready: AtomicBool::new(false), 87 | waker: AtomicWaker::new(), 88 | }); 89 | 90 | entry.insert(event.clone()); 91 | event 92 | }; 93 | 94 | if let Err(err) = 95 | unsafe { reactor.poller.add(event.fd, Event::readable(event.key)) } 96 | { 97 | let mut repo = reactor.repo.lock(); 98 | repo.remove(event.key); 99 | return Err(err); 100 | } 101 | 102 | Ok(Self { reactor, event }) 103 | } 104 | } 105 | 106 | impl Drop for IoGuard<'_> { 107 | fn drop(&mut self) { 108 | let mut repo = self.reactor.repo.lock(); 109 | repo.remove(self.event.key); 110 | self.reactor 111 | .poller 112 | .delete(unsafe { BorrowedFd::borrow_raw(self.event.fd) }) 113 | .ok(); 114 | } 115 | } 116 | 117 | let guard = IoGuard::new(self, fd)?; 118 | 119 | poll_fn(|cx| { 120 | let event = &*guard.event; 121 | if event.is_ready.load(Ordering::Acquire) { 122 | return Poll::Ready(Ok(())); 123 | } 124 | 125 | event.waker.register(cx.waker()); 126 | 127 | Poll::Pending 128 | }) 129 | .await 130 | } 131 | 132 | pub fn block_on(fut: F) -> io::Result 133 | where 134 | for<'r> F: FnOnce(&'r Self) -> BoxFuture<'r, T>, 135 | { 136 | let reactor = Reactor::new()?; 137 | let mut fut = fut(&reactor); 138 | 139 | let waker = reactor.waker(); 140 | let cx = &mut Context::from_waker(&waker); 141 | 142 | loop { 143 | if let Poll::Ready(t) = fut.as_mut().poll(cx) { 144 | return Ok(t); 145 | } 146 | 147 | reactor.block_until()?; 148 | } 149 | } 150 | } 151 | 152 | pub type BoxFuture<'a, T> = Pin + 'a>>; 153 | -------------------------------------------------------------------------------- /src/Executor.md: -------------------------------------------------------------------------------- 1 | # Executor 2 | 3 | 不过到目前为止,我们求值Future还是只有`block_on`函数,只能在单个线程上搞并发(通过`join`这些组合子),虽然在不少平台上这已经足够了(比如wasm目前多线程还没搞好,只有单线程),但在很多平台上却无法利用多线程的优势。 4 | 5 | 于是我们希望可以实现一个朴素的策略,利用多线程来poll Future: 6 | 7 | 1. 把`Future`丢到队列里, 8 | 2. 线程池里的空闲的线程则在队列中取`Future`轮询一次,然后晾一边, 9 | 3. 等待`Future`的waker被调用,丢到队列里。 10 | 11 | (图懒得画,大家自己脑补) -------------------------------------------------------------------------------- /src/Future.md: -------------------------------------------------------------------------------- 1 | # Future 2 | 3 | 我们还是得从Future讲起,**`Future`是rust中异步编程的最基础的抽象,表示一个异步的计算**。 -------------------------------------------------------------------------------- /src/Future的例子.md: -------------------------------------------------------------------------------- 1 | # Future的例子 2 | 3 | 刚刚通过接口的形式来介绍`Future`,那么我们怎么实现一个`Future`呢?除了`async {}`这种由编译器生成的`Future`以外,这里稍微给几个简单的例子。 4 | 5 | 6 | 7 | ## `yield_now()` 8 | 9 | 第一次轮询时返回`Pending`,第二次轮询时就绪。用于临时交还控制流用: 10 | 11 | ```rust 12 | struct YieldNow { 13 | is_ready: bool 14 | } 15 | 16 | impl Future for YieldNow { 17 | type Output = (); 18 | 19 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { 20 | if self.is_ready { 21 | return Poll::Ready(()); 22 | } 23 | 24 | self.is_ready = true; 25 | // 通知调用方,自己有“进展” 26 | cx.waker().wake_by_ref(); 27 | // 交还控制流 28 | Poll::Pending 29 | } 30 | } 31 | 32 | pub async fn yield_now() { 33 | YieldNow { is_ready: false }.await 34 | } 35 | ``` 36 | 37 | 38 | 39 | ## stdin 40 | 41 | 这个例子通过多线程模拟异步的IO,这里以stdin为例子(Rust标准库里的`Stdin`是同步阻塞的): 42 | 43 | ```rust 44 | // 提交到stdin线程处理的任务 45 | struct StdinTask { 46 | buf: Mutex>, 47 | waker: AtomicWaker, 48 | res: Mutex>>, 49 | } 50 | 51 | fn task_sender() -> &'static Sender> { 52 | static SENDER: OnceCell>> = OnceCell::new(); 53 | SENDER.get_or_init_bloking(|| { 54 | let (tx, rx) = mpsc::channel(); 55 | // 单起一个线程来处理stdin的读任务 56 | thread::spawn(move || { 57 | for mut task in rx { 58 | // 同步阻塞地读stdin 59 | let res = stdin().read(&mut task.buf.lock()); 60 | // 将读的结果塞回去 61 | *task.res.lock() = Some(res); 62 | // 通知已完成 63 | task.waker.take().map(Waker::wake); 64 | } 65 | }).unwrap(); 66 | 67 | tx 68 | }) 69 | } 70 | 71 | 72 | impl StdinTask { 73 | fn poll_read(&self, cx: &mut Contex<'_>) -> Poll<()> { 74 | // 检查任务是否完成 75 | if let Some(res) = self.res.lock() { 76 | return Poll::Ready(()) 77 | } 78 | 79 | // 重新注册一遍waker,因为有可能不是同一个运行时poll了。 80 | self.waker.register(cx.waker().clone()); 81 | Poll::Pending 82 | } 83 | } 84 | 85 | // 对外提供的接口 86 | pub async fn read_stdin(buf: Box<[u8]>) -> io::Result<(usize, Box)> { 87 | let task = Arc::new(StdinTask { 88 | buf: Mutex::new(buf), 89 | waker: AtomicWaker::new(), 90 | res: Mutex::new(None), 91 | }); 92 | 93 | // 发送到stdin线程处理 94 | task_sender().send(task.clone()).unwrap(); 95 | 96 | // 等待task被处理完 97 | poll_fn(|cx| task.poll_read(cx)).await; 98 | 99 | // 返回读的结果 100 | let task = Arc::try_unwrap(task).unwrap(); 101 | res.map(|i| (i, task.buf.into_inner())) 102 | } 103 | 104 | ``` 105 | 106 | 但这种用线程来模拟异步io的方式开销会比较大,我们需要通过系统提供的异步io来进行改进。 107 | 108 | 109 | 110 | 111 | 112 | ## `join`组合子 113 | 114 | 我们有了前面“单功能”的`Future`之后,我们还可以通过写一些组合子去把他们组合起来。比如`join`组合子用于并发执行多个`Future`,直到所有的`Future`都完成。 115 | 116 | ```rust 117 | #[pin_project] 118 | struct Join 119 | where 120 | FA: Future, 121 | FB: Future, 122 | { 123 | #[pin] 124 | fut_a: Option, 125 | a: Option<::Output>, 126 | #[pin] 127 | fut_b: Option, 128 | b: Option<::Output>, 129 | } 130 | 131 | impl Join 132 | where 133 | FA: Future, 134 | FB: Future, 135 | { 136 | type Output = (FA::Output, FB::Output); 137 | 138 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 139 | let this = self.project(); 140 | 141 | 142 | loop { 143 | // 当a fut未完成时poll 144 | if let Some(fut_a) = this.fut_a.as_pin_mut() { 145 | if let Poll::Ready(a) = fut_a.poll(cx) { 146 | this.a = Some(a); 147 | this.fut_a.set(None); 148 | continue; 149 | } 150 | } 151 | // 当b fut未完成时poll 152 | if let Some(fut_b) = this.fut_b.as_pin_mut() { 153 | if let Poll::Ready(b) = fut_b.poll(cx) { 154 | this.b = Some(b); 155 | this.fut_b.set(None); 156 | } 157 | } 158 | 159 | // 当两个future都成功时返回Ready 160 | // 否则返回Pending 161 | if let (Some(a), Some(b)) = (this.a, this.b) { 162 | return Poll::Ready((this.a.take().unwrap(), this.b.take().unwrap())); 163 | } else { 164 | return Poll::Pending; 165 | } 166 | } 167 | } 168 | } 169 | ``` 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /src/Future的接口.md: -------------------------------------------------------------------------------- 1 | # Future的接口 2 | 3 | `Future`的接口采用的是基于轮询的形式,而非更加常见的CPS形式: 4 | 5 | > 为了方便叙述,这里先去掉一些噪音,化简了一下现有接口 6 | 7 | ```rust 8 | /// 异步计算的抽象 9 | trait Future { 10 | type Output; 11 | 12 | /// 提供一个轮询的接口 13 | fn poll(&mut self, waker: Waker) -> Poll; 14 | } 15 | 16 | /// 轮询的结果 17 | enum Poll { 18 | Pending, 19 | Ready(T) 20 | } 21 | 22 | #[derive(Clone)] 23 | struct Waker { /*...*/ } 24 | impl Waker { 25 | /// 当异步计算有进展时调用,以通知轮询方进行下一轮的轮询 26 | fn wake(self); 27 | } 28 | ``` 29 | 30 | 我们需要拿到一个`Future`的值,我们需要不断地调用`poll`轮询它: 31 | 32 | * 当`poll`返回`Pending`的时候,表示`Future`还没完成,且暂时不需要占用当前控制流。从`Future`的角度来说,则是让出了当前的控制流,让我们可以做一些其它的事情。 33 | 34 | > 相比于同步阻塞的IO,异步IO当资源未就绪时返回Pending,可以避免陷入内核态,同时能减少上下文切换的开销 35 | 36 | * 当`poll`返回`Ready`的时候,则表示`Future`的计算已完成。 37 | 38 | 当然,除了`poll`以外,还可以取消一个`Future`,只需要不再轮询它,这时可以选择析构掉`Future`,释放掉里面的资源(这时对于`Future`来说,相当于在`.await`处panic了)。 39 | 40 | 其中`poll`还有一个参数`Waker`,当`Future`有进展时,就可以调用`.wake()`,来通知轮询方继续轮询`Future`。其中`Waker`,满足`Send`和 `Sync`,意味着`.wake()`方法可以在任何地方调用,比如说把`Waker`注册给OS,由OS来调用`.wake()`。 41 | 42 | > 注意:这个Waker参数并不一定是自上而下传递下来的,也有可能是poll中间构造的,甚至可以来自于别的运行时的。 43 | 44 | 45 | 46 | 于是对一个`Future`求值最基础的程序就长这样: 47 | 48 | ```rust 49 | // 轮询future 50 | loop { 51 | match fut.poll(waker) { 52 | Pending => { 53 | // 当异步计算不需要占用当前线程控制流的时候,会让出控制流,于是可以做一些其它事情 54 | } 55 | Ready(r) => { 56 | // 计算完成 57 | break r 58 | } 59 | } 60 | 61 | // 当`fut`有进一步进展时,可以进一步轮询。 62 | if todo!("fut 有进展") { 63 | continue; 64 | } 65 | } 66 | 67 | ``` 68 | 69 | 70 | 71 | 不过这里补充一点,`poll`一个`Future`的策略完全由轮询方来决定,不同的业务场景可以以不同的方式去轮询。`Waker`不调用的时候也轮询方也可以去`poll`一个`Future`;反过来`Waker`被调用了,也可以不立刻去`poll`。比如我们可以“马不停蹄”地轮询`Future` 72 | 73 | ```rust 74 | loop { 75 | // 返回`Pending`时,立刻继续`poll`,直到返回`Ready`, 76 | // 对于不希望线程休眠的程序的运行时,就可以这么设计 77 | if let Ready(r) = fut.poll(waker) { 78 | return r; 79 | } 80 | } 81 | ``` 82 | 83 | 84 | 85 | > 作为对比,这里简单地把基于CPS变换的异步计算的抽象列在这里: 86 | > 87 | > ```rust 88 | > trait Future { 89 | > type Output; 90 | > 91 | > /// 1.`schedule`和`callback`不应该阻塞,`callback`可能会被注册到一些地方 92 | > /// 2. 当异步计算**完成**时,`callback`就会被调用。 93 | > fn schedule(self, callback: Callback) 94 | > where 95 | > Callback: FnOnce(Self::Output) + Send + Sync 96 | > } 97 | > ``` 98 | > 99 | > 这时候对`Future`的求值就和我们在其他语言(比如js)中见到的类似了: 100 | > 101 | > ```rust 102 | > fut.schedule(|output| { 103 | > // 异步计算完成 104 | > do_something(output); 105 | > }); 106 | > ``` 107 | 108 | 109 | 110 | 其它更复杂的异步接口,在rust里也都可以,(也倾向于)设计成`poll_xxx`的形式(注:后续所有的waker参数都替换成现在的`Context`): 111 | 112 | * 异步流:`fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>` 113 | * 异步读:`fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll>` 114 | * 异步写:`fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll>` 115 | 116 | 等等。 117 | 118 | 119 | 120 | `Future`的接口其实是脱胎于`Iterator`的接口,都是通过**“外部”**轮询的方式来获取下一步结果,**同样也可以造出很多不同功能的组合子**(也可以叫Adapter)。相对于传统回调的方式,这种方式更符合Rust的哲学——*零开销抽象*,同时在borrow checker下这样的接口也更易使用一些。 121 | 122 | 这里更深入的讨论就不展开了,大家有兴趣可以看一下这些资料: 123 | 124 | * [Why async Rust?](https://without.boats/blog/why-async-rust/) 125 | 126 | * [Designing futures for Rust](https://aturon.github.io/blog/2016/09/07/futures-design/) -------------------------------------------------------------------------------- /src/Introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | 目前Rust使用最广的异步运行时是tokio,但tokio是一个十分完整的运行时,对于使用者来说几乎是一个黑盒,除非直接深入其源码,否则无法了解其结构与设计。这又使得tokio又像是一个无缝的蛋,当我们希望定制一些调度规则时,几乎是无从下手。 4 | 5 | 而smol作为tokio的“竞争对手”,其接口设计则好多了,将异步运行时拆分成相对独立的且小巧的几块(`async-io`, `async-executor`, `async-task`等等),能学习者能更好地了解异步运行时的结构,也能让使用者方便定制一些特定的规则。 6 | 7 | 那么这篇文章,大概顺着smol给出的设计思路,从头实现一个简单但齐全的异步运行时,让大家对异步运行时有一个基础的理解。 8 | 9 | 本文代码参考[这里](https://github.com/TOETOE55/async-rt-book/tree/master/simple-rt) 10 | 11 | (一直想写这篇文章,但耽搁了好久) 12 | 13 | -------------------------------------------------------------------------------- /src/Reactor.md: -------------------------------------------------------------------------------- 1 | # Reactor 2 | 3 | 因为rust标准库自带的IO默认都是同步且阻塞的(除了socket可以设置为non blocking模式),这就意味着我们需要有额外的线程来处理IO,才能让IO Future不阻塞在当前线程,就像刚刚异步的我们自己封装的stdin一样,而且单个线程也只能处理一个fd的IO操作。当我们有很多不同类型的IO要处理的时候,要么效率很低(单线程逐个处理),要么开销很高(开很多个线程处理IO) -------------------------------------------------------------------------------- /src/Reactor的设计.md: -------------------------------------------------------------------------------- 1 | # Reactor的设计 2 | 3 | > 参考 4 | > 5 | > * [async-io](https://github.com/smol-rs/async-io) 6 | > 7 | > * [Designing an Async Runtime for WASI 0.2](https://blog.yoshuawuyts.com/building-an-async-runtime-for-wasi/) 8 | 9 | 10 | 11 | 我们借助`polling`库,来实现一个reactor,提供统一管理IO的注册、IO事件监听以及唤醒的功能。 12 | 13 | 基础的设计是: 14 | 15 | 1. 有一个event loop不断监听注册在Reactor中的IO事件,当IO事件有响应时,调用对应的Waker 16 | 2. 被`block_on`求值的IO future,向Reactor注册IO事件(包括waker) 17 | 18 | ![reactor](./images/reactor.png) 19 | 20 | 这里`Reactor`最简单提供两个接口,`event_loop`和`register_readable`: 21 | 22 | ```rust 23 | // Reactor实例 24 | pub struct Reactor { 25 | // Poller实例 26 | poller: Poller, 27 | // 存储 28 | repo: Mutex>>, 29 | } 30 | 31 | // 代表一个IO 32 | struct IOEvent { 33 | fd: RawFd, 34 | key: usize, 35 | is_ready: AtomicBool, 36 | waker: AtomicWaker, 37 | } 38 | 39 | 40 | impl Reactor { 41 | // IO事件循环 42 | // 当存在fd就绪时,调用注册的waker 43 | pub fn event_loop(&self) -> io::Result<()>; 44 | 45 | // 注册一个可读事件 46 | // 当fd可读时返回 47 | pub async fn register_readable(&self, fd: BorrowedFd<'_>) -> io::Result<()>; 48 | } 49 | ``` 50 | 51 | 52 | 53 | 先来看看`event_loop`的实现,其做的事情就是: 54 | 55 | 1. 等待注册的IO就绪, 56 | 2. 调用对应的waker 57 | 58 | ```rust 59 | pub fn event_loop(&self) -> io::Result<()> { 60 | let mut events = Events::new(); 61 | 62 | loop { 63 | events.clear(); 64 | // 等待注册到poller的IO就绪 65 | match self.poller.wait(&mut events, None) { 66 | Ok(0) => {}, 67 | Ok(_) => { 68 | let repo = self.repo.lock(); 69 | for ev in events.iter() { 70 | // 调用waker 71 | if let Some(event) = repo.get(ev.key) { 72 | event.waker.take().map(Waker::wake); 73 | event.is_ready.swap(true, Ordering::Release); 74 | } 75 | } 76 | Ok(()) 77 | } 78 | Err(err) if err.kind() == ErrorKind::Interrupted => {}, 79 | Err(err) => return Err(err), 80 | } 81 | } 82 | 83 | Ok(()) 84 | } 85 | ``` 86 | 87 | 88 | 89 | 然后这里的注册的代码,写为一个异步函数,也方便通过RAII的方式去反注册: 90 | 91 | ```rust 92 | // 注册可读fd,直到fd就绪 93 | pub async fn register_readable(&self, fd: BorrowedFd<'_>) -> io::Result<()> { 94 | // IO RAII 95 | struct IOGuard<'r> { 96 | reactor: &'r Reactor, 97 | event: Arc, 98 | } 99 | 100 | impl<'r> IOGuard<'r> { 101 | // 构造FdGuard,并将fd注册到reactor中 102 | fn new(reactor: &'r Reactor, fd: BorrowedFd<'_>) -> io::Result { 103 | let event = { 104 | let mut repo = reactor.repo.lock(); 105 | let entry = repo.vacant_entry(); 106 | let event = Arc::new(IOEvent { 107 | fd: fd.as_raw_fd(), 108 | key: entry.key(), 109 | is_ready: AtomicBool::new(false), 110 | waker: AtomicWaker::new(), 111 | }); 112 | 113 | entry.insert(event.clone()); 114 | event 115 | }; 116 | 117 | // fd注册到poller里 118 | if let Err(err) = 119 | unsafe { reactor.poller.add(event.fd, Event::readable(event.key)) } 120 | { 121 | let mut repo = reactor.repo.lock(); 122 | repo.remove(event.key); 123 | return Err(err); 124 | } 125 | 126 | Ok(Self { reactor, event }) 127 | } 128 | } 129 | 130 | // 当完成或者取消时自动反注册 131 | impl Drop for IOGuard<'_> { 132 | fn drop(&mut self) { 133 | let mut repo = self.reactor.repo.lock(); 134 | repo.remove(self.event.key); 135 | self.reactor 136 | .poller 137 | .delete(unsafe { BorrowedFd::borrow_raw(self.event.fd) }) 138 | .ok(); 139 | } 140 | } 141 | 142 | let io_guard = IOGuard::new(self, fd)?; 143 | 144 | poll_fn(|cx| { 145 | let event = &*io_guard.event; 146 | // 等待reactor唤醒并改变状态 147 | if event.is_ready.load(Ordering::Acquire) { 148 | return Poll::Ready(Ok(())); 149 | } 150 | 151 | // 每次poll别忘记更新waker 152 | event.waker.register(cx.waker()); 153 | 154 | Poll::Pending 155 | }) 156 | .await 157 | } 158 | ``` 159 | 160 | 161 | 162 | 这个`register_readable`是用于IO future的实现的,这里仍然以stdin为例子: 163 | 164 | ```rust 165 | // 异步的stdin 166 | pub struct Stdin<'r> { 167 | reactor: &'r Reactor, 168 | stdin: io::Stdin, 169 | } 170 | 171 | impl<'r> Stdin<'r> { 172 | pub fn new(reactor: &'r Reactor) -> io::Result { 173 | let this = Self { 174 | reactor, 175 | stdin: io::stdin(), 176 | }; 177 | // 设置为异步的IO, 178 | // 之后阻塞时通过Read::read返回的错误码为WouldBlock 179 | rustix::io::ioctl_fionbio(&this.stdin, true)?; 180 | 181 | Ok(this) 182 | } 183 | 184 | pub async fn read(&self, buf: &mut [u8]) -> io::Result { 185 | loop { 186 | // 尝试读stdin 187 | match self.stdin.lock().read(buf) { 188 | Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} 189 | res => return res, 190 | } 191 | // 如果被阻塞则等待stdin就绪 192 | self.reactor.register_readable(self.stdin.as_fd()).await?; 193 | } 194 | } 195 | } 196 | 197 | ``` 198 | 199 | 200 | 201 | 有了`Reactor`,我们就可以单独跑一个线程来管理多个IO的注册与唤醒了,到这里才能体现出异步在IO密集的应用上的优势。虽然和前面的stdin实现都创建了一个额外的线程处理IO事件,但这里可以同时处理多个不同类型的IO,实现了所谓的IO的“复用”。 202 | 203 | ```rust 204 | let reactor = Reactor::new(); 205 | 206 | thread::scoped(|s| { 207 | // reactor io线程,用于处理IO事件 208 | s.spawn(|| reactor.event_loop().unwrap()); 209 | 210 | // 其它线程拿到Reactor可以用于创建IO对象 211 | s.spawn(|| { 212 | block_on(async { 213 | let mut buf = [0; 1000]; 214 | let mut buf = &mut buf[..]; 215 | let stdin = Stdin::new(reactor)?; 216 | 217 | while buf.len() > 0 { 218 | let x = stdin.read(buf).await?; 219 | println!("from stdin: {:?}", String::from_utf8_lossy(&buf[..x])); 220 | 221 | buf = &mut buf[x..]; 222 | yield_now().await; 223 | 224 | println!("yielding"); 225 | } 226 | 227 | println!("end"); 228 | Ok(()) 229 | }) 230 | }); 231 | }); 232 | ``` 233 | 234 | 235 | 236 | > 注:其实reactor的事件循环可以和block_on的轮询集成到一个循环里,这样甚至不需要多开一个线程。通过向reactor里注册一个特定的fd,在waker里进行IO操作,可以唤醒reactor。 237 | 238 | -------------------------------------------------------------------------------- /src/Rust异步基础部件.md: -------------------------------------------------------------------------------- 1 | # Rust异步基础部件 2 | 3 | 目前Rust使用最广的异步运行时是tokio,但tokio是一个十分完整的运行时,对于使用者来说几乎是一个黑盒,除非直接深入其源码,否则无法了解其结构与设计。这又使得tokio又像是一个无缝的蛋,当我们希望定制一些调度规则时,几乎是无从下手。 4 | 5 | 而smol作为tokio的“竞争对手”,其接口设计则好多了,将异步运行时拆分成相对独立的且小巧的几块(`async-io`, `async-executor`, `async-task`等等),能学习者能更好地了解异步运行时的结构,也能让使用者方便定制一些特定的规则。 6 | 7 | 那么这篇文章,大概顺着smol给出的设计思路,从头实现一个简单但齐全的异步运行时,让大家对异步运行时有一个基础的理解。(一直想写这篇文章,但耽搁了好久) 8 | 9 | 10 | 11 | ## Future 12 | 13 | 我们还是得从Future讲起,**`Future`是rust中异步编程的最基础的抽象,表示一个异步的计算**。 14 | 15 | 16 | 17 | ### Future的接口 18 | 19 | `Future`的接口采用的是基于轮询的形式,而非更加常见的CPS形式: 20 | 21 | > 为了方便叙述,这里先去掉一些噪音,化简了一下现有接口 22 | 23 | ```rust 24 | /// 异步计算的抽象 25 | trait Future { 26 | type Output; 27 | 28 | /// 提供一个轮询的接口 29 | fn poll(&mut self, waker: Waker) -> Poll; 30 | } 31 | 32 | /// 轮询的结果 33 | enum Poll { 34 | Pending, 35 | Ready(T) 36 | } 37 | 38 | #[derive(Clone)] 39 | struct Waker { /*...*/ } 40 | impl Waker { 41 | /// 当异步计算有进展时调用,以通知轮询方进行下一轮的轮询 42 | fn wake(self); 43 | } 44 | ``` 45 | 46 | 我们需要拿到一个`Future`的值,我们需要不断地调用`poll`轮询它: 47 | 48 | * 当`poll`返回`Pending`的时候,表示`Future`还没完成,且暂时不需要占用当前控制流。从`Future`的角度来说,则是让出了当前的控制流,让我们可以做一些其它的事情。 49 | 50 | > 相比于同步阻塞的IO,异步IO当资源未就绪时返回Pending,可以避免陷入内核态,同时能减少上下文切换的开销 51 | * 当`poll`返回`Ready`的时候,则表示`Future`的计算已完成。 52 | 53 | 当然,除了`poll`以外,还可以取消一个`Future`,只需要不再轮询它,这时可以选择析构掉`Future`,释放掉里面的资源(这时对于`Future`来说,相当于在`.await`处panic了)。 54 | 55 | 其中`poll`还有一个参数`Waker`,当`Future`有进展时,就可以调用`.wake()`,来通知轮询方继续轮询`Future`。其中`Waker`,满足`Send`和 `Sync`,意味着`.wake()`方法可以在任何地方调用,比如说把`Waker`注册给OS,由OS来调用`.wake()`。 56 | 57 | > 注意:这个Waker参数并不一定是自上而下传递下来的,也有可能是poll中间构造的,甚至可以来自于别的运行时的。 58 | 59 | 60 | 61 | 于是对一个`Future`求值最基础的程序就长这样: 62 | 63 | ```rust 64 | 65 | // 轮询future 66 | loop { 67 | match fut.poll(waker) { 68 | Pending => { 69 | // 当异步计算不需要占用当前线程控制流的时候,会让出控制流,于是可以做一些其它事情 70 | } 71 | Ready(r) => { 72 | // 计算完成 73 | break r 74 | } 75 | } 76 | 77 | // 当`fut`有进一步进展时,可以进一步轮询。 78 | if todo!("fut 有进展") { 79 | continue; 80 | } 81 | } 82 | 83 | ``` 84 | 85 | 86 | 87 | 不过这里补充一点,`poll`一个`Future`的策略完全由轮询方来决定,不同的业务场景可以以不同的方式去轮询。`Waker`不调用的时候也轮询方也可以去`poll`一个`Future`;反过来`Waker`被调用了,也可以不立刻去`poll`。比如我们可以“马不停蹄”地轮询`Future` 88 | 89 | ```rust 90 | loop { 91 | // 返回`Pending`时,立刻继续`poll`,直到返回`Ready`, 92 | // 对于不希望线程休眠的程序的运行时,就可以这么设计 93 | if let Ready(r) = fut.poll(waker) { 94 | return r; 95 | } 96 | } 97 | ``` 98 | 99 | 100 | 101 | > 作为对比,这里简单地把基于CPS变换的异步计算的抽象列在这里: 102 | > 103 | > ```rust 104 | > trait Future { 105 | > type Output; 106 | > 107 | > /// 1.`schedule`和`callback`不应该阻塞,`callback`可能会被注册到一些地方 108 | > /// 2. 当异步计算**完成**时,`callback`就会被调用。 109 | > fn schedule(self, callback: Callback) 110 | > where 111 | > Callback: FnOnce(Self::Output) + Send + Sync 112 | > } 113 | > ``` 114 | > 115 | > 这时候对`Future`的求值就和我们在其他语言(比如js)中见到的类似了: 116 | > 117 | > ```rust 118 | > fut.schedule(|output| { 119 | > // 异步计算完成 120 | > do_something(output); 121 | > }); 122 | > ``` 123 | 124 | 125 | 126 | 其它更复杂的异步接口,在rust里也都可以,(也倾向于)设计成`poll_xxx`的形式(注:后续所有的waker参数都替换成现在的`Context`): 127 | 128 | * 异步流:`fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>` 129 | * 异步读:`fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll>` 130 | * 异步写:`fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll>` 131 | 132 | 等等。 133 | 134 | 135 | 136 | `Future`的接口其实是脱胎于`Iterator`的接口,都是通过**“外部”**轮询的方式来获取下一步结果,**同样也可以造出很多不同功能的组合子**(也可以叫Adapter)。相对于传统回调的方式,这种方式更符合Rust的哲学——*零开销抽象*,同时在borrow checker下这样的接口也更易使用一些。 137 | 138 | 这里更深入的讨论就不展开了,大家有兴趣可以看一下这些资料: 139 | 140 | * https://without.boats/blog/why-async-rust/ 141 | 142 | * https://aturon.github.io/blog/2016/09/07/futures-design/ 143 | 144 | 145 | 146 | ### Future实现的例子 147 | 148 | 刚刚通过接口的形式来介绍`Future`,那么我们怎么实现一个`Future`呢?除了`async {}`这种由编译器生成的`Future`以外,这里稍微给几个简单的例子。 149 | 150 | 151 | 152 | #### `yield_now()` 153 | 154 | 第一次轮询时返回`Pending`,第二次轮询时就绪。用于临时交还控制流用: 155 | 156 | ```rust 157 | struct YieldNow { 158 | is_ready: bool 159 | } 160 | 161 | impl Future for YieldNow { 162 | type Output = (); 163 | 164 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { 165 | if self.is_ready { 166 | return Poll::Ready(()); 167 | } 168 | 169 | self.is_ready = true; 170 | // 通知调用方,自己有“进展” 171 | cx.waker().wake_by_ref(); 172 | // 交还控制流 173 | Poll::Pending 174 | } 175 | } 176 | 177 | pub async fn yield_now() { 178 | YieldNow { is_ready: false }.await 179 | } 180 | ``` 181 | 182 | 183 | 184 | #### stdin 185 | 186 | 这个例子通过多线程模拟异步的IO,这里以stdin为例子(Rust标准库里的`Stdin`是同步阻塞的): 187 | 188 | ```rust 189 | // 提交到stdin线程处理的任务 190 | struct StdinTask { 191 | buf: Mutex>, 192 | waker: AtomicWaker, 193 | res: Mutex>>, 194 | } 195 | 196 | fn task_sender() -> &'static Sender> { 197 | static SENDER: OnceCell>> = OnceCell::new(); 198 | SENDER.get_or_init_bloking(|| { 199 | let (tx, rx) = mpsc::channel(); 200 | // 单起一个线程来处理stdin的读任务 201 | thread::spawn(move || { 202 | for mut task in rx { 203 | // 同步阻塞地读stdin 204 | let res = stdin().read(&mut task.buf.lock()); 205 | // 将读的结果塞回去 206 | *task.res.lock() = Some(res); 207 | // 通知已完成 208 | task.waker.take().map(Waker::wake); 209 | } 210 | }).unwrap(); 211 | 212 | tx 213 | }) 214 | } 215 | 216 | 217 | impl StdinTask { 218 | fn poll_read(&self, cx: &mut Contex<'_>) -> Poll<()> { 219 | // 检查任务是否完成 220 | if let Some(res) = self.res.lock() { 221 | return Poll::Ready(()) 222 | } 223 | 224 | // 重新注册一遍waker,因为有可能不是同一个运行时poll了。 225 | self.waker.register(cx.waker().clone()); 226 | Poll::Pending 227 | } 228 | } 229 | 230 | // 对外提供的接口 231 | pub async fn read_stdin(buf: Box<[u8]>) -> io::Result<(usize, Box)> { 232 | let task = Arc::new(StdinTask { 233 | buf: Mutex::new(buf), 234 | waker: AtomicWaker::new(), 235 | res: Mutex::new(None), 236 | }); 237 | 238 | // 发送到stdin线程处理 239 | task_sender().send(task.clone()).unwrap(); 240 | 241 | // 等待task被处理完 242 | poll_fn(|cx| task.poll_read(cx)).await; 243 | 244 | // 返回读的结果 245 | let task = Arc::try_unwrap(task).unwrap(); 246 | res.map(|i| (i, task.buf.into_inner())) 247 | } 248 | 249 | ``` 250 | 251 | 但这种用线程来模拟异步io的方式开销会比较大,我们需要通过系统提供的异步io来进行改进。 252 | 253 | 254 | 255 | 256 | 257 | #### `join`组合子 258 | 259 | 我们有了前面“单功能”的`Future`之后,我们还可以通过写一些组合子去把他们组合起来。比如`join`组合子用于并发执行多个`Future`,直到所有的`Future`都完成。 260 | 261 | ```rust 262 | #[pin_project] 263 | struct Join 264 | where 265 | FA: Future, 266 | FB: Future, 267 | { 268 | #[pin] 269 | fut_a: Option, 270 | a: Option<::Output>, 271 | #[pin] 272 | fut_b: Option, 273 | b: Option<::Output>, 274 | } 275 | 276 | impl Join 277 | where 278 | FA: Future, 279 | FB: Future, 280 | { 281 | type Output = (FA::Output, FB::Output); 282 | 283 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 284 | let this = self.project(); 285 | 286 | 287 | loop { 288 | // 当a fut未完成时poll 289 | if let Some(fut_a) = this.fut_a.as_pin_mut() { 290 | if let Poll::Ready(a) = fut_a.poll(cx) { 291 | this.a = Some(a); 292 | this.fut_a.set(None); 293 | continue; 294 | } 295 | } 296 | // 当b fut未完成时poll 297 | if let Some(fut_b) = this.fut_b.as_pin_mut() { 298 | if let Poll::Ready(b) = fut_b.poll(cx) { 299 | this.b = Some(b); 300 | this.fut_b.set(None); 301 | } 302 | } 303 | 304 | // 当两个future都成功时返回Ready 305 | // 否则返回Pending 306 | if let (Some(a), Some(b)) = (this.a, this.b) { 307 | return Poll::Ready((this.a.take().unwrap(), this.b.take().unwrap())); 308 | } else { 309 | return Poll::Pending; 310 | } 311 | } 312 | } 313 | } 314 | ``` 315 | 316 | 317 | 318 | 319 | 320 | ## block_on 321 | 322 | 前面花了比较大的笔墨在介绍`Future`上。现在终于可以去聊聊异步运行时的事了。 323 | 324 | 325 | 326 | 异步运行时的最低要求是可以求值一个`Future`,这个接口的签名写起来就是: 327 | 328 | ```rust 329 | /// 阻塞地求值一个`Future`,并把future的结果返回出来 330 | fn block_on(fut: impl Future) -> T; 331 | ``` 332 | 333 | > 因为rust本身的运行时只能直接调用同步的接口,所以提供一个同步的`block_on`接口是必要的,作为“异步转同步”的入口。 334 | 335 | 336 | 337 | 按照上面`Future`求值的模板,如果除了求值`Future`之外什么都不干的话,我们就可以在`poll`一个`Future`返回`Pending`时,休眠线程: 338 | 339 | ```rust 340 | fn block_on(fut: impl Future) -> T { 341 | // 当前线程的`parker`和`unparker` 342 | let (parker, unparker) = parking::pair(); 343 | // waker在调用`.wake()`时unpark当前线程 344 | let waker = waker_fn(move || { unparker.unpark(); }); 345 | let cx = &mut Context::from_waker(&waker); 346 | 347 | // 轮询`Future` 348 | let mut fut = pin!(fut); 349 | loop { 350 | if let Poll::Ready(t) = fut.as_mut().poll(cx) { 351 | return t; 352 | } 353 | 354 | // 返回`Pending`就休眠线程,等待`waker`被调用 355 | // 注:如果waker已经被调用过了,这里就不会阻塞。 356 | parker.park() 357 | } 358 | } 359 | ``` 360 | 361 | 这就是`block_on`最基础的实现(当然如果在嵌入式里没有park/unpark,也只有单线程的情况下就另当别论),也是一个异步运行时最简单的形式。之后我们在这个基础上不断添加新的东西来完善它。 362 | 363 | 364 | 365 | 有了`block_on`我们就可以写一个简单的异步程序了: 366 | 367 | ```rust 368 | fn main() { 369 | // 读一次stdin,并打印 370 | let fut_a = async { 371 | let mut buf = Box::<[u8]>::from([0;100]); 372 | let mut i = 0; 373 | (i, buf) = read_stdin(buf).await?; 374 | println!("{:?}", String::from_utf8_lossy(&buf[..i])); 375 | Ok(()) 376 | }; 377 | 378 | let fut_b = async { 379 | yield_now().await; 380 | println!("yield 1"); 381 | yield_now().await; 382 | println!("yield 2"); 383 | }; 384 | 385 | // 并发执行fut_a, fut_b 386 | let fut = join(fut_a, fut_b); 387 | 388 | // 执行fut 389 | block_on(fut); 390 | } 391 | ``` 392 | 393 | 394 | 395 | 396 | 397 | ## Reactor 398 | 399 | 因为rust标准库自带的IO默认都是同步且阻塞的(除了socket可以设置为non blocking模式),这就意味着我们需要有额外的线程来处理IO,才能让IO Future不阻塞在当前线程,就像刚刚异步的我们自己封装的stdin一样,而且单个线程也只能处理一个fd的IO操作。当我们有很多不同类型的IO要处理的时候,要么效率很低(单线程逐个处理),要么开销很高(开很多个线程处理IO)。 400 | 401 | 402 | 403 | ### 异步IO与Poller 404 | 405 | 既然标准库没有异步的IO接口,我们把目标转向OS自身提供的异步IO接口,比如linux下的`epoll`。`epoll`主要提供了三个接口: 406 | 407 | ```c 408 | /// 创建新的epoll实例 409 | int epoll_create(int size); 410 | 411 | /// 向 epoll 实例中添加、修改或删除文件描述符 412 | int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event); 413 | 414 | /// 等待 epoll 实例中的文件描述符准备就绪。 415 | /// 这个函数会阻塞调用线程,到存在文件描述符就绪。 416 | int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 417 | ``` 418 | 419 | 核心思路是: 420 | 421 | 1. 把“关心”的IO的fd添加到`epoll`实例中(比如关心fd是否能写) 422 | 2. 然后调用`epoll_wait`,阻塞调用线程到存在关心的fd已就绪时,线程继续运行。 423 | 3. 这时候就能直接给已就绪的fd进行IO操作。 424 | 425 | 相对于标准库同步阻塞的IO操作来说,`epoll`把 *等待fd就绪* 这一步单独抽离了出来(也就是上面的第二步),允许同时监听多个fd,且允许超时。**这就允许用额外一个线程就可以处理多个IO事件,或者是通过时间片的方式来处理IO事件。** 426 | 427 | 428 | 429 | 不同的操作系统都有类似的接口,rust里已经有crate进行统一封装,比如说[`mio`](https://crates.io/crates/mio), [`polling`](https://crates.io/crates/polling)。比如说`polling`提供提了以下接口,大体结构与`epoll`一致: 430 | 431 | ```rust 432 | /// Poller实例, 433 | /// * linux下是epoll实例 434 | /// * mac, iOS下是kqueue实例 435 | /// * windows下是iocp实例 436 | struct Poller { /*...*/ } 437 | 438 | impl Poller { 439 | /// 创建一个poller实例 440 | pub fn new() -> Result; 441 | 442 | /// 往poller实例中添加fd,及关心的事件 443 | pub unsafe fn add( 444 | &self, 445 | /// unix下是fd, windows下是socket 446 | source: impl AsRawSource, 447 | interest: Event 448 | ) -> Result<()>; 449 | 450 | /// 更新fd关心的事件, 451 | /// 比如关心fd是否可读,改成是否可写 452 | pub fn modify(&self, source: impl AsSource, interest: Event) -> Result<()>; 453 | 454 | /// 删除关心的fd 455 | pub fn delete(&self, source: impl AsSource) -> Result<()>; 456 | 457 | /// 阻塞调用线程直到 458 | /// * 存在关心的fd就绪,或 459 | /// * 超时 460 | /// * 唤醒poller实例 461 | pub fn wait( 462 | &self, 463 | events: &mut Events, 464 | /// * `None`为不设置超时 465 | /// * `Some(0)`为不阻塞 466 | timeout: Option 467 | ) -> Result; 468 | 469 | /// 唤醒poller实例 470 | pub fn notify(&self) -> Result<()>; 471 | } 472 | ``` 473 | 474 | 475 | 476 | 这是`polling`库的Example(截自README) 477 | 478 | > ```rust 479 | >use polling::{Event, Poller}; 480 | > use std::net::TcpListener; 481 | > 482 | > // Create a TCP listener. 483 | > let socket = TcpListener::bind("127.0.0.1:8000")?; 484 | > socket.set_nonblocking(true)?; 485 | > let key = 7; // Arbitrary key identifying the socket. 486 | > 487 | > // Create a poller and register interest in readability on the socket. 488 | > let poller = Poller::new()?; 489 | > poller.add(&socket, Event::readable(key))?; 490 | > 491 | > // The event loop. 492 | > let mut events = Vec::new(); 493 | > loop { 494 | > // Wait for at least one I/O event. 495 | > events.clear(); 496 | > poller.wait(&mut events, None)?; 497 | > 498 | > for ev in &events { 499 | > if ev.key == key { 500 | > // Perform a non-blocking accept operation. 501 | > socket.accept()?; 502 | > // Set interest in the next readability event. 503 | > poller.modify(&socket, Event::readable(key))?; 504 | > } 505 | > } 506 | > } 507 | > ``` 508 | 509 | 510 | 511 | 512 | 513 | ### Reactor的设计 514 | 515 | > 参考 516 | > 517 | > * [async-io](https://github.com/smol-rs/async-io) 518 | > 519 | > * https://blog.yoshuawuyts.com/building-an-async-runtime-for-wasi/ 520 | 521 | 522 | 523 | 我们借助`polling`库,来实现一个reactor,提供统一管理IO的注册、IO事件监听以及唤醒的功能。 524 | 525 | 基础的设计是: 526 | 527 | 1. 有一个event loop不断监听注册在Reactor中的IO事件,当IO事件有响应时,调用对应的Waker 528 | 2. 被`block_on`求值的IO future,向Reactor注册IO事件(包括waker) 529 | 530 | ![reactor](images\reactor.png) 531 | 532 | 这里`Reactor`最简单提供两个接口,`event_loop`和`register_readable`: 533 | 534 | ```rust 535 | // Reactor实例 536 | pub struct Reactor { 537 | // Poller实例 538 | poller: Poller, 539 | // 存储 540 | repo: Mutex>>, 541 | } 542 | 543 | // 代表一个IO 544 | struct IOEvent { 545 | fd: RawFd, 546 | key: usize, 547 | is_ready: AtomicBool, 548 | waker: AtomicWaker, 549 | } 550 | 551 | 552 | impl Reactor { 553 | // IO事件循环 554 | // 当存在fd就绪时,调用注册的waker 555 | pub fn event_loop(&self) -> io::Result<()>; 556 | 557 | // 注册一个可读事件 558 | // 当fd可读时返回 559 | pub async fn register_readable(&self, fd: BorrowedFd<'_>) -> io::Result<()>; 560 | } 561 | ``` 562 | 563 | 564 | 565 | 先来看看`event_loop`的实现,其做的事情就是: 566 | 567 | 1. 等待注册的IO就绪, 568 | 2. 调用对应的waker 569 | 570 | ```rust 571 | pub fn event_loop(&self) -> io::Result<()> { 572 | let mut events = Events::new(); 573 | 574 | loop { 575 | events.clear(); 576 | // 等待注册到poller的IO就绪 577 | match self.poller.wait(&mut events, None) { 578 | Ok(0) => {}, 579 | Ok(_) => { 580 | let repo = self.repo.lock(); 581 | for ev in events.iter() { 582 | // 调用waker 583 | if let Some(event) = repo.get(ev.key) { 584 | event.waker.take().map(Waker::wake); 585 | event.is_ready.swap(true, Ordering::Release); 586 | } 587 | } 588 | Ok(()) 589 | } 590 | Err(err) if err.kind() == ErrorKind::Interrupted => {}, 591 | Err(err) => return Err(err), 592 | } 593 | } 594 | 595 | Ok(()) 596 | } 597 | ``` 598 | 599 | 600 | 601 | 然后这里的注册的代码,写为一个异步函数,也方便通过RAII的方式去反注册: 602 | 603 | ```rust 604 | // 注册可读fd,直到fd就绪 605 | pub async fn register_readable(&self, fd: BorrowedFd<'_>) -> io::Result<()> { 606 | // IO RAII 607 | struct IOGuard<'r> { 608 | reactor: &'r Reactor, 609 | event: Arc, 610 | } 611 | 612 | impl<'r> IOGuard<'r> { 613 | // 构造FdGuard,并将fd注册到reactor中 614 | fn new(reactor: &'r Reactor, fd: BorrowedFd<'_>) -> io::Result { 615 | let event = { 616 | let mut repo = reactor.repo.lock(); 617 | let entry = repo.vacant_entry(); 618 | let event = Arc::new(IOEvent { 619 | fd: fd.as_raw_fd(), 620 | key: entry.key(), 621 | is_ready: AtomicBool::new(false), 622 | waker: AtomicWaker::new(), 623 | }); 624 | 625 | entry.insert(event.clone()); 626 | event 627 | }; 628 | 629 | // fd注册到poller里 630 | if let Err(err) = 631 | unsafe { reactor.poller.add(event.fd, Event::readable(event.key)) } 632 | { 633 | let mut repo = reactor.repo.lock(); 634 | repo.remove(event.key); 635 | return Err(err); 636 | } 637 | 638 | Ok(Self { reactor, event }) 639 | } 640 | } 641 | 642 | // 当完成或者取消时自动反注册 643 | impl Drop for IOGuard<'_> { 644 | fn drop(&mut self) { 645 | let mut repo = self.reactor.repo.lock(); 646 | repo.remove(self.event.key); 647 | self.reactor 648 | .poller 649 | .delete(unsafe { BorrowedFd::borrow_raw(self.event.fd) }) 650 | .ok(); 651 | } 652 | } 653 | 654 | let io_guard = IOGuard::new(self, fd)?; 655 | 656 | poll_fn(|cx| { 657 | let event = &*io_guard.event; 658 | // 等待reactor唤醒并改变状态 659 | if event.is_ready.load(Ordering::Acquire) { 660 | return Poll::Ready(Ok(())); 661 | } 662 | 663 | // 每次poll别忘记更新waker 664 | event.waker.register(cx.waker()); 665 | 666 | Poll::Pending 667 | }) 668 | .await 669 | } 670 | ``` 671 | 672 | 673 | 674 | 这个`register_readable`是用于IO future的实现的,这里仍然以stdin为例子: 675 | 676 | ```rust 677 | // 异步的stdin 678 | pub struct Stdin<'r> { 679 | reactor: &'r Reactor, 680 | stdin: io::Stdin, 681 | } 682 | 683 | impl<'r> Stdin<'r> { 684 | pub fn new(reactor: &'r Reactor) -> io::Result { 685 | let this = Self { 686 | reactor, 687 | stdin: io::stdin(), 688 | }; 689 | // 设置为异步的IO, 690 | // 之后阻塞时通过Read::read返回的错误码为WouldBlock 691 | rustix::io::ioctl_fionbio(&this.stdin, true)?; 692 | 693 | Ok(this) 694 | } 695 | 696 | pub async fn read(&self, buf: &mut [u8]) -> io::Result { 697 | loop { 698 | // 尝试读stdin 699 | match self.stdin.lock().read(buf) { 700 | Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} 701 | res => return res, 702 | } 703 | // 如果被阻塞则等待stdin就绪 704 | self.reactor.register_readable(self.stdin.as_fd()).await?; 705 | } 706 | } 707 | } 708 | 709 | ``` 710 | 711 | 712 | 713 | 有了`Reactor`,我们就可以单独跑一个线程来管理多个IO的注册与唤醒了,到这里才能体现出异步在IO密集的应用上的优势。虽然和前面的stdin实现都创建了一个额外的线程处理IO事件,但这里可以同时处理多个不同类型的IO,实现了所谓的IO的“复用”。 714 | 715 | ```rust 716 | let reactor = Reactor::new(); 717 | 718 | thread::scoped(|s| { 719 | // reactor io线程,用于处理IO事件 720 | s.spawn(|| reactor.event_loop().unwrap()); 721 | 722 | // 其它线程拿到Reactor可以用于创建IO对象 723 | s.spawn(|| { 724 | block_on(async { 725 | let mut buf = [0; 1000]; 726 | let mut buf = &mut buf[..]; 727 | let stdin = Stdin::new(reactor)?; 728 | 729 | while buf.len() > 0 { 730 | let x = stdin.read(buf).await?; 731 | println!("from stdin: {:?}", String::from_utf8_lossy(&buf[..x])); 732 | 733 | buf = &mut buf[x..]; 734 | yield_now().await; 735 | 736 | println!("yielding"); 737 | } 738 | 739 | println!("end"); 740 | Ok(()) 741 | }) 742 | }); 743 | }); 744 | ``` 745 | 746 | 747 | 748 | > 注:其实reactor的事件循环可以和block_on的轮询集成到一个循环里,这样甚至不需要多开一个线程。通过向reactor里注册一个特定的fd,在waker里进行IO操作,可以唤醒reactor。 749 | 750 | 751 | 752 | ## Executor 753 | 754 | 不过到目前为止,我们求值Future还是只有`block_on`函数,只能在单个线程上搞并发(通过`join`这些组合子),虽然在不少平台上这已经足够了(比如wasm目前多线程还没搞好,只有单线程),但在很多平台上却无法利用多线程的优势。 755 | 756 | 于是我们希望可以实现一个朴素的策略,利用多线程来poll Future: 757 | 758 | 1. 把`Future`丢到队列里, 759 | 2. 线程池里的空闲的线程则在队列中取`Future`轮询一次,然后晾一边, 760 | 3. 等待`Future`的waker被调用,丢到队列里。 761 | 762 | 763 | 764 | ### Task 765 | 766 | 我们把“等待Future的waker被调用,然后又丢到队列里”,这个部分单独抽出来,这个步骤称之为一次调度,被调度的称为一个`Task`。 767 | 768 | `async_task`已经为我们封装好了这个抽象,它提供的核心接口为: 769 | 770 | - `spawn` 创建一个task 771 | - `Runnable::run` poll 一下 task中的future 772 | - `schedule` 当task中的future被唤醒时,把task传到调度器里 773 | 774 | ```rust 775 | // 接受一个`Future`和`schedule`创建一个`Task` 776 | pub fn spawn(future: F, schedule: S) -> (Runnable, Task) 777 | where 778 | // 因为Future可以丢到任何线程中执行,所以要求Send 779 | F: Future + Send + 'static, 780 | F::Output: Send + 'static, 781 | // 因为schedule可以被任何地方随时调用,所以要求Send + Sync 782 | S: Fn(Runnable) + Send + Sync + 'static; 783 | 784 | impl Runnable { 785 | // 执行一遍Future::poll() 786 | // 当wake被调用时,将task传到schedule里 787 | pub fn run(self) -> bool; 788 | 789 | // 直接把task传给schedule 790 | pub fn schedule(self); 791 | } 792 | 793 | // 当Future完成后,才会唤醒一次poll task的运行时 794 | impl Future for Task { 795 | /*...*/ 796 | } 797 | ``` 798 | 799 | 800 | 801 | 比如当`schedule`的作用是将task丢到队列里,就可以写成: 802 | 803 | ```rust 804 | let (tx, rx) = channel(); 805 | // 把task丢到队列里 806 | let schedule = move |runnable| tx.send(runnable).unwrap(); 807 | 808 | // 创建一个task 809 | let (runnable, task) = async_task::spawn(yield_now(), schedule); 810 | 811 | // 执行一遍poll, 812 | // 当waker被调用时,就会把task传给schedule 813 | runnable.run(); 814 | 815 | // 这里task没法完成,因为传到队列里后没有人执行 816 | ``` 817 | 818 | 819 | 820 | 至于`async_task`是如何实现的,这里就不做太多展开了,大家可以直接去看源码。这里做点提示,这里`Task`, `Runnable`以及`Waker`背后都指向创建时的Future+schedule,因为职责不一样所以提供的方法不一样: 821 | 822 | 1. `Task`用于看future是否完成,以及取最终结果 823 | 2. `Runnable`用于执行`Future::poll` 824 | 3. `Waker`用于把Task传给`schedule` 825 | 826 | 827 | 828 | ### 一个完整的Executor的例子 829 | 830 | 这里的`Executor`提供两个核心的接口: 831 | 832 | ```rust 833 | pub struct Executor<'a> { 834 | /// 这里用的是异步mpmc channel 835 | tx: Sender, 836 | rx: Receiver, 837 | 838 | /// Makes the `'a` lifetime invariant. 839 | _marker: PhantomData>, 840 | } 841 | 842 | impl<'a> Executor<'a> { 843 | // 创建一个Task,并丢到Executor中 844 | pub fn spawn(&self, future: impl Future + Send + 'a) -> Task; 845 | 846 | // 当executor里有新的task时,就会拿出来执行 847 | pub async fn execute(&self); 848 | } 849 | ``` 850 | 851 | 852 | 853 | 有了`async_task`我们就很容易实现这两个接口: 854 | 855 | ```rust 856 | pub fn spawn(&self, future: impl Future + Send + 'a) -> Task { 857 | let tx = self.tx.clone(); 858 | let schedule = move |runnable| tx.send(runnable).unwrap(); 859 | 860 | // 创建一个task,并直接丢到队列里 861 | let (runnable, task) = unsafe { async_task::spawn_unchecked(future, schedule) }; 862 | runnable.schedule(); 863 | 864 | task 865 | } 866 | 867 | pub async fn execute(&self) { 868 | // 不断从队列里取task,并轮询 869 | while let Some(runnable) = self.rx.next().await { 870 | runnable.run(); 871 | } 872 | } 873 | ``` 874 | 875 | 876 | 877 | > 注:同`Reactor::event_loop`,`Executor::execute`也可以集成到`block_on`中。 878 | 879 | 880 | 881 | 有了`Executor`我们就可以利用多个线程来并发执行异步代码了: 882 | 883 | ```rust 884 | let reactor = Reactor::new(); 885 | let executor = Executor::new(); 886 | 887 | thread::scoped(|s| { 888 | // reactor io线程,用于处理IO事件 889 | s.spawn(|| reactor.event_loop().unwrap()); 890 | 891 | // 起8个线程作为线程池,来并发执行task 892 | for _ in 0..8 { 893 | s.spawn(|| { 894 | block_on(executor.execute()); 895 | }); 896 | } 897 | 898 | // 创建异步任务,丢到Executor中执行 899 | executor.spawn(async { 900 | let mut buf = [0; 1000]; 901 | let mut buf = &mut buf[..]; 902 | let stdin = Stdin::new(reactor)?; 903 | 904 | while buf.len() > 0 { 905 | let x = stdin.read(buf).await?; 906 | println!("from stdin: {:?}", String::from_utf8_lossy(&buf[..x])); 907 | 908 | buf = &mut buf[x..]; 909 | } 910 | Ok(()) 911 | }).detach(); 912 | 913 | // 这个也会丢到Executor,然后被并发执行 914 | executor.spawn(async { 915 | yield_now().await; 916 | println!("yield 1"); 917 | yield_now().await; 918 | println!("yield 2"); 919 | }).detach(); 920 | 921 | }); 922 | ``` 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | # 结论 931 | 932 | 至此,我们已经获得了一个功能相对完整的异步运行时了。这里将异步运行时打碎成`block_on`, `Reactor`, `Executor`三个最基础的组件,其实我们已经拥有了几乎没有上限的定制运行时的能力,我们完全可以控制Future的执行时机,可以拥有什么权限等等。 933 | 934 | 可惜,现在tokio当道,tokio则几乎没有定制的能力,作为使用者没法拒绝得了tokio全家桶,哎。 935 | 936 | 937 | 938 | 最后画一个图来总结下 939 | 940 | 941 | 942 | 943 | 944 | ![image-20240225201714205](images\relation.png) 945 | 946 | 1. 异步运行时最基础的一个功能是`block_on()`,对一个`Future`进行求值,其最简单实现就是对`Future`进行轮询`poll`。 947 | 2. 我们通过引入一个`Reactor`(一个IO的event loop),获得了异步IO的能力,比如文件读写、Timer等。 948 | 3. 然后我们再加入一个`Executor`时(利用多线程对Task进行调度),我们就获得了多线程并发的能力,也就是`spawn()`接口。 949 | 4. 同时有了`block_on()`, `Executor`和`Reactor`之后,我们就能获得一个功能相对完整的异步运行时。(当然,不一定要有`Executor`和`Reactor`这两个抽象,tokio就是将两者合二为一) 950 | 951 | 952 | 953 | -------------------------------------------------------------------------------- /src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](./Introduction.md) 4 | 5 | - [Future](./Future.md) 6 | - [Future的接口](./Future的接口.md) 7 | - [Future的例子](./Future的例子.md) 8 | - [block_on](./block_on.md) 9 | - [Reactor](./Reactor.md) 10 | - [异步IO与poller](./异步IO与Poller.md) 11 | - [Reactor的设计](./Reactor的设计.md) 12 | - [Executor](./Executor.md) 13 | - [Task](./Task.md) 14 | - [完整的Executor](./一个完整的Executor例子.md) 15 | - [结论](./结论.md) -------------------------------------------------------------------------------- /src/Task.md: -------------------------------------------------------------------------------- 1 | # Task 2 | 3 | 我们把“等待Future的waker被调用,然后又丢到队列里”,这个部分单独抽出来,这个步骤称之为一次调度,被调度的称为一个`Task`。 4 | 5 | `async_task`已经为我们封装好了这个抽象,它提供的核心接口为: 6 | 7 | - `spawn` 创建一个task 8 | - `Runnable::run` poll 一下 task中的future 9 | - `schedule` 当task中的future被唤醒时,把task传到调度器里 10 | 11 | ```rust 12 | // 接受一个`Future`和`schedule`创建一个`Task` 13 | pub fn spawn(future: F, schedule: S) -> (Runnable, Task) 14 | where 15 | // 因为Future可以丢到任何线程中执行,所以要求Send 16 | F: Future + Send + 'static, 17 | F::Output: Send + 'static, 18 | // 因为schedule可以被任何地方随时调用,所以要求Send + Sync 19 | S: Fn(Runnable) + Send + Sync + 'static; 20 | 21 | impl Runnable { 22 | // 执行一遍Future::poll() 23 | // 当wake被调用时,将task传到schedule里 24 | pub fn run(self) -> bool; 25 | 26 | // 直接把task传给schedule 27 | pub fn schedule(self); 28 | } 29 | 30 | // 当Future完成后,才会唤醒一次poll task的运行时 31 | impl Future for Task { 32 | /*...*/ 33 | } 34 | ``` 35 | 36 | 37 | 38 | 比如当`schedule`的作用是将task丢到队列里,就可以写成: 39 | 40 | ```rust 41 | let (tx, rx) = channel(); 42 | // 把task丢到队列里 43 | let schedule = move |runnable| tx.send(runnable).unwrap(); 44 | 45 | // 创建一个task 46 | let (runnable, task) = async_task::spawn(yield_now(), schedule); 47 | 48 | // 执行一遍poll, 49 | // 当waker被调用时,就会把task传给schedule 50 | runnable.run(); 51 | 52 | // 这里task没法完成,因为传到队列里后没有人执行 53 | ``` 54 | 55 | > 这里插入一些个人的想法: 56 | > 57 | > rust在客户端最常见的使用方式之一就是作为跨端的业务层使用,被上层业务代码所调用。比如UIKit(iOS), Android等。 58 | > 这些 *宿主* 通常都会自带一个异步的运行时。 59 | > 利用`async_task`我们甚至可以利用“宿主语言”的异步运行时进行调度。 60 | > 61 | 62 | 63 | 至于`async_task`是如何实现的,这里就不做太多展开了,大家可以直接去看源码。这里做点提示,这里`Task`, `Runnable`以及`Waker`背后都指向创建时的Future+schedule,因为职责不一样所以提供的方法不一样: 64 | 65 | 1. `Task`用于看future是否完成,以及取最终结果 66 | 2. `Runnable`用于执行`Future::poll` 67 | 3. `Waker`用于把Task传给`schedule` -------------------------------------------------------------------------------- /src/block_on.md: -------------------------------------------------------------------------------- 1 | # block_on 2 | 3 | 前面花了比较大的笔墨在介绍`Future`上。现在终于可以去聊聊异步运行时的事了。 4 | 5 | 6 | 7 | 异步运行时的最低要求是可以求值一个`Future`,这个接口的签名写起来就是: 8 | 9 | ```rust 10 | /// 阻塞地求值一个`Future`,并把future的结果返回出来 11 | fn block_on(fut: impl Future) -> T; 12 | ``` 13 | 14 | > 因为rust本身的运行时只能直接调用同步的接口,所以提供一个同步的`block_on`接口是必要的,作为“异步转同步”的入口。 15 | 16 | 17 | 18 | 按照上面`Future`求值的模板,如果除了求值`Future`之外什么都不干的话,我们就可以在`poll`一个`Future`返回`Pending`时,休眠线程: 19 | 20 | ```rust 21 | fn block_on(fut: impl Future) -> T { 22 | // 当前线程的`parker`和`unparker` 23 | let (parker, unparker) = parking::pair(); 24 | // waker在调用`.wake()`时unpark当前线程 25 | let waker = waker_fn(move || { unparker.unpark(); }); 26 | let cx = &mut Context::from_waker(&waker); 27 | 28 | // 轮询`Future` 29 | let mut fut = pin!(fut); 30 | loop { 31 | if let Poll::Ready(t) = fut.as_mut().poll(cx) { 32 | return t; 33 | } 34 | 35 | // 返回`Pending`就休眠线程,等待`waker`被调用 36 | // 注:如果waker已经被调用过了,这里就不会阻塞。 37 | parker.park() 38 | } 39 | } 40 | ``` 41 | 42 | 这就是`block_on`最基础的实现(当然如果在嵌入式里没有park/unpark,也只有单线程的情况下就另当别论),也是一个异步运行时最简单的形式。之后我们在这个基础上不断添加新的东西来完善它。 43 | 44 | 45 | 46 | 有了`block_on`我们就可以写一个简单的异步程序了: 47 | 48 | ```rust 49 | fn main() { 50 | // 读一次stdin,并打印 51 | let fut_a = async { 52 | let mut buf = Box::<[u8]>::from([0;100]); 53 | let mut i = 0; 54 | (i, buf) = read_stdin(buf).await?; 55 | println!("{:?}", String::from_utf8_lossy(&buf[..i])); 56 | Ok(()) 57 | }; 58 | 59 | let fut_b = async { 60 | yield_now().await; 61 | println!("yield 1"); 62 | yield_now().await; 63 | println!("yield 2"); 64 | }; 65 | 66 | // 并发执行fut_a, fut_b 67 | let fut = join(fut_a, fut_b); 68 | 69 | // 执行fut 70 | block_on(fut); 71 | } 72 | ``` 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/images/reactor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TOETOE55/async-rt-book/cf8a5bf2f571307da98aca1ca7bc737d06037f54/src/images/reactor.png -------------------------------------------------------------------------------- /src/images/relation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TOETOE55/async-rt-book/cf8a5bf2f571307da98aca1ca7bc737d06037f54/src/images/relation.png -------------------------------------------------------------------------------- /src/一个完整的Executor例子.md: -------------------------------------------------------------------------------- 1 | # 一个完整的Executor的例子 2 | 3 | 这里的`Executor`提供两个核心的接口: 4 | 5 | ```rust 6 | pub struct Executor<'a> { 7 | /// 这里用的是异步mpmc channel 8 | tx: Sender, 9 | rx: Receiver, 10 | 11 | /// Makes the `'a` lifetime invariant. 12 | _marker: PhantomData>, 13 | } 14 | 15 | impl<'a> Executor<'a> { 16 | // 创建一个Task,并丢到Executor中 17 | pub fn spawn(&self, future: impl Future + Send + 'a) -> Task; 18 | 19 | // 当executor里有新的task时,就会拿出来执行 20 | pub async fn execute(&self); 21 | } 22 | ``` 23 | 24 | 25 | 26 | 有了`async_task`我们就很容易实现这两个接口: 27 | 28 | ```rust 29 | pub fn spawn(&self, future: impl Future + Send + 'a) -> Task { 30 | let tx = self.tx.clone(); 31 | let schedule = move |runnable| tx.send(runnable).unwrap(); 32 | 33 | // 创建一个task,并直接丢到队列里 34 | let (runnable, task) = unsafe { async_task::spawn_unchecked(future, schedule) }; 35 | runnable.schedule(); 36 | 37 | task 38 | } 39 | 40 | pub async fn execute(&self) { 41 | // 不断从队列里取task,并轮询 42 | let mut rx = self.rx.stream(); 43 | while let Some(runnable) = rx.next().await { 44 | runnable.run(); 45 | } 46 | } 47 | ``` 48 | 49 | 50 | 51 | > 注:同`Reactor::event_loop`,`Executor::execute`也可以集成到`block_on`中。 52 | 53 | 54 | 55 | 有了`Executor`我们就可以利用多个线程来并发执行异步代码了: 56 | 57 | ```rust 58 | let reactor = Reactor::new(); 59 | let executor = Executor::new(); 60 | 61 | thread::scope(|s| { 62 | // reactor io线程,用于处理IO事件 63 | s.spawn(|| reactor.event_loop().unwrap()); 64 | 65 | // 起8个线程作为线程池,来并发执行task 66 | for _ in 0..8 { 67 | s.spawn(|| { 68 | block_on(executor.execute()); 69 | }); 70 | } 71 | 72 | // 创建异步任务,丢到Executor中执行 73 | executor.spawn(async { 74 | let mut buf = [0; 1000]; 75 | let mut buf = &mut buf[..]; 76 | let stdin = Stdin::new(reactor).unwrap(); 77 | 78 | while buf.len() > 0 { 79 | let x = stdin.read(buf).await.unwrap(); 80 | println!("from stdin: {:?}", String::from_utf8_lossy(&buf[..x])); 81 | 82 | buf = &mut buf[x..]; 83 | } 84 | }).detach(); 85 | 86 | // 这个也会丢到Executor,然后被并发执行 87 | executor.spawn(async { 88 | yield_now().await; 89 | println!("yield 1"); 90 | yield_now().await; 91 | println!("yield 2"); 92 | }).detach(); 93 | 94 | }); 95 | ``` 96 | 97 | -------------------------------------------------------------------------------- /src/异步IO与Poller.md: -------------------------------------------------------------------------------- 1 | # 异步IO与Poller 2 | 3 | 既然标准库没有异步的IO接口,我们把目标转向OS自身提供的异步IO接口,比如linux下的`epoll`。`epoll`主要提供了三个接口: 4 | 5 | ```c 6 | /// 创建新的epoll实例 7 | int epoll_create(int size); 8 | 9 | /// 向 epoll 实例中添加、修改或删除文件描述符 10 | int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event); 11 | 12 | /// 等待 epoll 实例中的文件描述符准备就绪。 13 | /// 这个函数会阻塞调用线程,到存在文件描述符就绪。 14 | int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 15 | ``` 16 | 17 | 核心思路是: 18 | 19 | 1. 把“关心”的IO的fd添加到`epoll`实例中(比如关心fd是否能写) 20 | 2. 然后调用`epoll_wait`,阻塞调用线程到存在关心的fd已就绪时,线程继续运行。 21 | 3. 这时候就能直接给已就绪的fd进行IO操作。 22 | 23 | 相对于标准库同步阻塞的IO操作来说,`epoll`把 *等待fd就绪* 这一步单独抽离了出来(也就是上面的第二步),允许同时监听多个fd,且允许超时。**这就允许用额外一个线程就可以处理多个IO事件,或者是通过时间片的方式来处理IO事件。** 24 | 25 | 不过要注意的是,**`epoll`并不支持普通的文件**,如果把文件fd添加到`epoll`里会返回`EPERM`错误。普通的文件读写需要用到aio或者使用一个线程池把同步转成异步。 26 | `epoll`目前支持的fd是: 27 | 28 | * 网络socket,比如说TCP, UDP等 29 | * `timerfd` 30 | * `signalfd` 31 | * `inotify` 32 | * pipe 33 | * 子进程 34 | * 终端相关,比如`stdin`, `stdout`, `stderr` 35 | * `epoll`本身(可以加到其它`epoll`实例中) 36 | * 等等 37 | 38 | 39 | 不同的操作系统都有类似的接口,rust里已经有crate进行统一封装,比如说[`mio`](https://crates.io/crates/mio), [`polling`](https://crates.io/crates/polling)。比如说`polling`提供提了以下接口,大体结构与`epoll`一致: 40 | 41 | ```rust 42 | /// Poller实例, 43 | /// * linux下是epoll实例 44 | /// * mac, iOS下是kqueue实例 45 | /// * windows下是iocp实例 46 | struct Poller { /*...*/ } 47 | 48 | impl Poller { 49 | /// 创建一个poller实例 50 | pub fn new() -> Result; 51 | 52 | /// 往poller实例中添加fd,及关心的事件 53 | pub unsafe fn add( 54 | &self, 55 | /// unix下是fd, windows下是socket 56 | source: impl AsRawSource, 57 | interest: Event 58 | ) -> Result<()>; 59 | 60 | /// 更新fd关心的事件, 61 | /// 比如关心fd是否可读,改成是否可写 62 | pub fn modify(&self, source: impl AsSource, interest: Event) -> Result<()>; 63 | 64 | /// 删除关心的fd 65 | pub fn delete(&self, source: impl AsSource) -> Result<()>; 66 | 67 | /// 阻塞调用线程直到 68 | /// * 存在关心的fd就绪,或 69 | /// * 超时 70 | /// * 唤醒poller实例 71 | pub fn wait( 72 | &self, 73 | events: &mut Events, 74 | /// * `None`为不设置超时 75 | /// * `Some(0)`为不阻塞 76 | timeout: Option 77 | ) -> Result; 78 | 79 | /// 唤醒poller实例 80 | pub fn notify(&self) -> Result<()>; 81 | } 82 | ``` 83 | 84 | 85 | 86 | 这是`polling`库的Example(截自README) 87 | 88 | > ```rust 89 | > use polling::{Event, Poller}; 90 | > use std::net::TcpListener; 91 | > 92 | > // Create a TCP listener. 93 | > let socket = TcpListener::bind("127.0.0.1:8000")?; 94 | > socket.set_nonblocking(true)?; 95 | > let key = 7; // Arbitrary key identifying the socket. 96 | > 97 | > // Create a poller and register interest in readability on the socket. 98 | > let poller = Poller::new()?; 99 | > poller.add(&socket, Event::readable(key))?; 100 | > 101 | > // The event loop. 102 | > let mut events = Vec::new(); 103 | > loop { 104 | > // Wait for at least one I/O event. 105 | > events.clear(); 106 | > poller.wait(&mut events, None)?; 107 | > 108 | > for ev in &events { 109 | > if ev.key == key { 110 | > // Perform a non-blocking accept operation. 111 | > socket.accept()?; 112 | > // Set interest in the next readability event. 113 | > poller.modify(&socket, Event::readable(key))?; 114 | > } 115 | > } 116 | > } 117 | > ``` 118 | 119 | -------------------------------------------------------------------------------- /src/结论.md: -------------------------------------------------------------------------------- 1 | # 结论 2 | 3 | 至此,我们已经获得了一个功能相对完整的异步运行时了。这里将异步运行时打碎成`block_on`, `Reactor`, `Executor`三个最基础的组件,其实我们已经拥有了几乎没有上限的定制运行时的能力,我们完全可以控制Future的执行时机,可以拥有什么权限等等。 4 | 5 | 可惜,现在tokio当道,tokio则几乎没有定制的能力,作为使用者没法拒绝得了tokio全家桶,哎。 6 | 7 | 8 | 9 | 最后画一个图来总结下 10 | 11 | 12 | 13 | ![relation](./images/relation.png) 14 | 15 | 1. 异步运行时最基础的一个功能是`block_on()`,对一个`Future`进行求值,其最简单实现就是对`Future`进行轮询`poll`。 16 | 2. 我们通过引入一个`Reactor`(一个IO的event loop),获得了异步IO的能力,比如文件读写、Timer等。 17 | 3. 然后我们再加入一个`Executor`时(利用多线程对Task进行调度),我们就获得了多线程并发的能力,也就是`spawn()`接口。 18 | 4. 同时有了`block_on()`, `Executor`和`Reactor`之后,我们就能获得一个功能相对完整的异步运行时。(当然,不一定要有`Executor`和`Reactor`这两个抽象,tokio就是将两者合二为一) 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /theme/book.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Fix back button cache problem 4 | window.onunload = function () { }; 5 | 6 | // Global variable, shared between modules 7 | function playground_text(playground, hidden = true) { 8 | let code_block = playground.querySelector("code"); 9 | 10 | if (window.ace && code_block.classList.contains("editable")) { 11 | let editor = window.ace.edit(code_block); 12 | return editor.getValue(); 13 | } else if (hidden) { 14 | return code_block.textContent; 15 | } else { 16 | return code_block.innerText; 17 | } 18 | } 19 | 20 | (function codeSnippets() { 21 | function fetch_with_timeout(url, options, timeout = 6000) { 22 | return Promise.race([ 23 | fetch(url, options), 24 | new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)) 25 | ]); 26 | } 27 | 28 | var playgrounds = Array.from(document.querySelectorAll(".playground")); 29 | if (playgrounds.length > 0) { 30 | fetch_with_timeout("https://play.rust-lang.org/meta/crates", { 31 | headers: { 32 | 'Content-Type': "application/json", 33 | }, 34 | method: 'POST', 35 | mode: 'cors', 36 | }) 37 | .then(response => response.json()) 38 | .then(response => { 39 | // get list of crates available in the rust playground 40 | let playground_crates = response.crates.map(item => item["id"]); 41 | playgrounds.forEach(block => handle_crate_list_update(block, playground_crates)); 42 | }); 43 | } 44 | 45 | function handle_crate_list_update(playground_block, playground_crates) { 46 | // update the play buttons after receiving the response 47 | update_play_button(playground_block, playground_crates); 48 | 49 | // and install on change listener to dynamically update ACE editors 50 | if (window.ace) { 51 | let code_block = playground_block.querySelector("code"); 52 | if (code_block.classList.contains("editable")) { 53 | let editor = window.ace.edit(code_block); 54 | editor.addEventListener("change", function (e) { 55 | update_play_button(playground_block, playground_crates); 56 | }); 57 | // add Ctrl-Enter command to execute rust code 58 | editor.commands.addCommand({ 59 | name: "run", 60 | bindKey: { 61 | win: "Ctrl-Enter", 62 | mac: "Ctrl-Enter" 63 | }, 64 | exec: _editor => run_rust_code(playground_block) 65 | }); 66 | } 67 | } 68 | } 69 | 70 | // updates the visibility of play button based on `no_run` class and 71 | // used crates vs ones available on http://play.rust-lang.org 72 | function update_play_button(pre_block, playground_crates) { 73 | var play_button = pre_block.querySelector(".play-button"); 74 | 75 | // skip if code is `no_run` 76 | if (pre_block.querySelector('code').classList.contains("no_run")) { 77 | play_button.classList.add("hidden"); 78 | return; 79 | } 80 | 81 | // get list of `extern crate`'s from snippet 82 | var txt = playground_text(pre_block); 83 | var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g; 84 | var snippet_crates = []; 85 | var item; 86 | while (item = re.exec(txt)) { 87 | snippet_crates.push(item[1]); 88 | } 89 | 90 | // check if all used crates are available on play.rust-lang.org 91 | var all_available = snippet_crates.every(function (elem) { 92 | return playground_crates.indexOf(elem) > -1; 93 | }); 94 | 95 | if (all_available) { 96 | play_button.classList.remove("hidden"); 97 | } else { 98 | play_button.classList.add("hidden"); 99 | } 100 | } 101 | 102 | function run_rust_code(code_block) { 103 | var result_block = code_block.querySelector(".result"); 104 | if (!result_block) { 105 | result_block = document.createElement('code'); 106 | result_block.className = 'result hljs language-bash'; 107 | 108 | code_block.append(result_block); 109 | } 110 | 111 | let text = playground_text(code_block); 112 | let classes = code_block.querySelector('code').classList; 113 | let edition = "2015"; 114 | if(classes.contains("edition2018")) { 115 | edition = "2018"; 116 | } else if(classes.contains("edition2021")) { 117 | edition = "2021"; 118 | } 119 | var params = { 120 | version: "stable", 121 | optimize: "0", 122 | code: text, 123 | edition: edition 124 | }; 125 | 126 | if (text.indexOf("#![feature") !== -1) { 127 | params.version = "nightly"; 128 | } 129 | 130 | result_block.innerText = "Running..."; 131 | 132 | fetch_with_timeout("https://play.rust-lang.org/evaluate.json", { 133 | headers: { 134 | 'Content-Type': "application/json", 135 | }, 136 | method: 'POST', 137 | mode: 'cors', 138 | body: JSON.stringify(params) 139 | }) 140 | .then(response => response.json()) 141 | .then(response => { 142 | if (response.result.trim() === '') { 143 | result_block.innerText = "No output"; 144 | result_block.classList.add("result-no-output"); 145 | } else { 146 | result_block.innerText = response.result; 147 | result_block.classList.remove("result-no-output"); 148 | } 149 | }) 150 | .catch(error => result_block.innerText = "Playground Communication: " + error.message); 151 | } 152 | 153 | // Syntax highlighting Configuration 154 | hljs.configure({ 155 | tabReplace: ' ', // 4 spaces 156 | languages: [], // Languages used for auto-detection 157 | }); 158 | 159 | let code_nodes = Array 160 | .from(document.querySelectorAll('code')) 161 | // Don't highlight `inline code` blocks in headers. 162 | .filter(function (node) {return !node.parentElement.classList.contains("header"); }); 163 | 164 | if (window.ace) { 165 | // language-rust class needs to be removed for editable 166 | // blocks or highlightjs will capture events 167 | code_nodes 168 | .filter(function (node) {return node.classList.contains("editable"); }) 169 | .forEach(function (block) { block.classList.remove('language-rust'); }); 170 | 171 | code_nodes 172 | .filter(function (node) {return !node.classList.contains("editable"); }) 173 | .forEach(function (block) { hljs.highlightBlock(block); }); 174 | } else { 175 | code_nodes.forEach(function (block) { hljs.highlightBlock(block); }); 176 | } 177 | 178 | // Adding the hljs class gives code blocks the color css 179 | // even if highlighting doesn't apply 180 | code_nodes.forEach(function (block) { block.classList.add('hljs'); }); 181 | 182 | Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) { 183 | 184 | var lines = Array.from(block.querySelectorAll('.boring')); 185 | // If no lines were hidden, return 186 | if (!lines.length) { return; } 187 | block.classList.add("hide-boring"); 188 | 189 | var buttons = document.createElement('div'); 190 | buttons.className = 'buttons'; 191 | buttons.innerHTML = ""; 192 | 193 | // add expand button 194 | var pre_block = block.parentNode; 195 | pre_block.insertBefore(buttons, pre_block.firstChild); 196 | 197 | pre_block.querySelector('.buttons').addEventListener('click', function (e) { 198 | if (e.target.classList.contains('fa-eye')) { 199 | e.target.classList.remove('fa-eye'); 200 | e.target.classList.add('fa-eye-slash'); 201 | e.target.title = 'Hide lines'; 202 | e.target.setAttribute('aria-label', e.target.title); 203 | 204 | block.classList.remove('hide-boring'); 205 | } else if (e.target.classList.contains('fa-eye-slash')) { 206 | e.target.classList.remove('fa-eye-slash'); 207 | e.target.classList.add('fa-eye'); 208 | e.target.title = 'Show hidden lines'; 209 | e.target.setAttribute('aria-label', e.target.title); 210 | 211 | block.classList.add('hide-boring'); 212 | } 213 | }); 214 | }); 215 | 216 | if (window.playground_copyable) { 217 | Array.from(document.querySelectorAll('pre code')).forEach(function (block) { 218 | var pre_block = block.parentNode; 219 | if (!pre_block.classList.contains('playground')) { 220 | var buttons = pre_block.querySelector(".buttons"); 221 | if (!buttons) { 222 | buttons = document.createElement('div'); 223 | buttons.className = 'buttons'; 224 | pre_block.insertBefore(buttons, pre_block.firstChild); 225 | } 226 | 227 | var clipButton = document.createElement('button'); 228 | clipButton.className = 'fa fa-copy clip-button'; 229 | clipButton.title = 'Copy to clipboard'; 230 | clipButton.setAttribute('aria-label', clipButton.title); 231 | clipButton.innerHTML = ''; 232 | 233 | buttons.insertBefore(clipButton, buttons.firstChild); 234 | } 235 | }); 236 | } 237 | 238 | // Process playground code blocks 239 | Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) { 240 | // Add play button 241 | var buttons = pre_block.querySelector(".buttons"); 242 | if (!buttons) { 243 | buttons = document.createElement('div'); 244 | buttons.className = 'buttons'; 245 | pre_block.insertBefore(buttons, pre_block.firstChild); 246 | } 247 | 248 | var runCodeButton = document.createElement('button'); 249 | runCodeButton.className = 'fa fa-play play-button'; 250 | runCodeButton.hidden = true; 251 | runCodeButton.title = 'Run this code'; 252 | runCodeButton.setAttribute('aria-label', runCodeButton.title); 253 | 254 | buttons.insertBefore(runCodeButton, buttons.firstChild); 255 | runCodeButton.addEventListener('click', function (e) { 256 | run_rust_code(pre_block); 257 | }); 258 | 259 | if (window.playground_copyable) { 260 | var copyCodeClipboardButton = document.createElement('button'); 261 | copyCodeClipboardButton.className = 'fa fa-copy clip-button'; 262 | copyCodeClipboardButton.innerHTML = ''; 263 | copyCodeClipboardButton.title = 'Copy to clipboard'; 264 | copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title); 265 | 266 | buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild); 267 | } 268 | 269 | let code_block = pre_block.querySelector("code"); 270 | if (window.ace && code_block.classList.contains("editable")) { 271 | var undoChangesButton = document.createElement('button'); 272 | undoChangesButton.className = 'fa fa-history reset-button'; 273 | undoChangesButton.title = 'Undo changes'; 274 | undoChangesButton.setAttribute('aria-label', undoChangesButton.title); 275 | 276 | buttons.insertBefore(undoChangesButton, buttons.firstChild); 277 | 278 | undoChangesButton.addEventListener('click', function () { 279 | let editor = window.ace.edit(code_block); 280 | editor.setValue(editor.originalCode); 281 | editor.clearSelection(); 282 | }); 283 | } 284 | }); 285 | })(); 286 | 287 | (function themes() { 288 | var html = document.querySelector('html'); 289 | var themeToggleButton = document.getElementById('theme-toggle'); 290 | var themePopup = document.getElementById('theme-list'); 291 | var themeColorMetaTag = document.querySelector('meta[name="theme-color"]'); 292 | var stylesheets = { 293 | ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"), 294 | tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"), 295 | highlight: document.querySelector("[href$='highlight.css']"), 296 | }; 297 | 298 | function showThemes() { 299 | themePopup.style.display = 'block'; 300 | themeToggleButton.setAttribute('aria-expanded', true); 301 | themePopup.querySelector("button#" + get_theme()).focus(); 302 | } 303 | 304 | function updateThemeSelected() { 305 | themePopup.querySelectorAll('.theme-selected').forEach(function (el) { 306 | el.classList.remove('theme-selected'); 307 | }); 308 | themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected'); 309 | } 310 | 311 | function hideThemes() { 312 | themePopup.style.display = 'none'; 313 | themeToggleButton.setAttribute('aria-expanded', false); 314 | themeToggleButton.focus(); 315 | } 316 | 317 | function get_theme() { 318 | var theme; 319 | try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { } 320 | if (theme === null || theme === undefined) { 321 | return default_theme; 322 | } else { 323 | return theme; 324 | } 325 | } 326 | 327 | function set_theme(theme, store = true) { 328 | let ace_theme; 329 | 330 | if (theme == 'coal' || theme == 'navy') { 331 | stylesheets.ayuHighlight.disabled = true; 332 | stylesheets.tomorrowNight.disabled = false; 333 | stylesheets.highlight.disabled = true; 334 | 335 | ace_theme = "ace/theme/tomorrow_night"; 336 | } else if (theme == 'ayu') { 337 | stylesheets.ayuHighlight.disabled = false; 338 | stylesheets.tomorrowNight.disabled = true; 339 | stylesheets.highlight.disabled = true; 340 | ace_theme = "ace/theme/tomorrow_night"; 341 | } else { 342 | stylesheets.ayuHighlight.disabled = true; 343 | stylesheets.tomorrowNight.disabled = true; 344 | stylesheets.highlight.disabled = false; 345 | ace_theme = "ace/theme/dawn"; 346 | } 347 | 348 | setTimeout(function () { 349 | themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor; 350 | }, 1); 351 | 352 | if (window.ace && window.editors) { 353 | window.editors.forEach(function (editor) { 354 | editor.setTheme(ace_theme); 355 | }); 356 | } 357 | 358 | var previousTheme = get_theme(); 359 | 360 | if (store) { 361 | try { localStorage.setItem('mdbook-theme', theme); } catch (e) { } 362 | } 363 | 364 | html.classList.remove(previousTheme); 365 | html.classList.add(theme); 366 | updateThemeSelected(); 367 | } 368 | 369 | // Set theme 370 | var theme = get_theme(); 371 | 372 | set_theme(theme, false); 373 | 374 | themeToggleButton.addEventListener('click', function () { 375 | if (themePopup.style.display === 'block') { 376 | hideThemes(); 377 | } else { 378 | showThemes(); 379 | } 380 | }); 381 | 382 | themePopup.addEventListener('click', function (e) { 383 | var theme; 384 | if (e.target.className === "theme") { 385 | theme = e.target.id; 386 | } else if (e.target.parentElement.className === "theme") { 387 | theme = e.target.parentElement.id; 388 | } else { 389 | return; 390 | } 391 | set_theme(theme); 392 | }); 393 | 394 | themePopup.addEventListener('focusout', function(e) { 395 | // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below) 396 | if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) { 397 | hideThemes(); 398 | } 399 | }); 400 | 401 | // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628 402 | document.addEventListener('click', function(e) { 403 | if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) { 404 | hideThemes(); 405 | } 406 | }); 407 | 408 | document.addEventListener('keydown', function (e) { 409 | if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } 410 | if (!themePopup.contains(e.target)) { return; } 411 | 412 | switch (e.key) { 413 | case 'Escape': 414 | e.preventDefault(); 415 | hideThemes(); 416 | break; 417 | case 'ArrowUp': 418 | e.preventDefault(); 419 | var li = document.activeElement.parentElement; 420 | if (li && li.previousElementSibling) { 421 | li.previousElementSibling.querySelector('button').focus(); 422 | } 423 | break; 424 | case 'ArrowDown': 425 | e.preventDefault(); 426 | var li = document.activeElement.parentElement; 427 | if (li && li.nextElementSibling) { 428 | li.nextElementSibling.querySelector('button').focus(); 429 | } 430 | break; 431 | case 'Home': 432 | e.preventDefault(); 433 | themePopup.querySelector('li:first-child button').focus(); 434 | break; 435 | case 'End': 436 | e.preventDefault(); 437 | themePopup.querySelector('li:last-child button').focus(); 438 | break; 439 | } 440 | }); 441 | })(); 442 | 443 | (function sidebar() { 444 | var html = document.querySelector("html"); 445 | var sidebar = document.getElementById("sidebar"); 446 | var sidebarLinks = document.querySelectorAll('#sidebar a'); 447 | var sidebarToggleButton = document.getElementById("sidebar-toggle"); 448 | var sidebarResizeHandle = document.getElementById("sidebar-resize-handle"); 449 | var firstContact = null; 450 | 451 | function showSidebar() { 452 | html.classList.remove('sidebar-hidden') 453 | html.classList.add('sidebar-visible'); 454 | Array.from(sidebarLinks).forEach(function (link) { 455 | link.setAttribute('tabIndex', 0); 456 | }); 457 | sidebarToggleButton.setAttribute('aria-expanded', true); 458 | sidebar.setAttribute('aria-hidden', false); 459 | try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { } 460 | } 461 | 462 | 463 | var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle'); 464 | 465 | function toggleSection(ev) { 466 | ev.currentTarget.parentElement.classList.toggle('expanded'); 467 | } 468 | 469 | Array.from(sidebarAnchorToggles).forEach(function (el) { 470 | el.addEventListener('click', toggleSection); 471 | }); 472 | 473 | function hideSidebar() { 474 | html.classList.remove('sidebar-visible') 475 | html.classList.add('sidebar-hidden'); 476 | Array.from(sidebarLinks).forEach(function (link) { 477 | link.setAttribute('tabIndex', -1); 478 | }); 479 | sidebarToggleButton.setAttribute('aria-expanded', false); 480 | sidebar.setAttribute('aria-hidden', true); 481 | try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { } 482 | } 483 | 484 | // Toggle sidebar 485 | sidebarToggleButton.addEventListener('click', function sidebarToggle() { 486 | if (html.classList.contains("sidebar-hidden")) { 487 | var current_width = parseInt( 488 | document.documentElement.style.getPropertyValue('--sidebar-width'), 10); 489 | if (current_width < 150) { 490 | document.documentElement.style.setProperty('--sidebar-width', '150px'); 491 | } 492 | showSidebar(); 493 | } else if (html.classList.contains("sidebar-visible")) { 494 | hideSidebar(); 495 | } else { 496 | if (getComputedStyle(sidebar)['transform'] === 'none') { 497 | hideSidebar(); 498 | } else { 499 | showSidebar(); 500 | } 501 | } 502 | }); 503 | 504 | sidebarResizeHandle.addEventListener('mousedown', initResize, false); 505 | 506 | function initResize(e) { 507 | window.addEventListener('mousemove', resize, false); 508 | window.addEventListener('mouseup', stopResize, false); 509 | html.classList.add('sidebar-resizing'); 510 | } 511 | function resize(e) { 512 | var pos = (e.clientX - sidebar.offsetLeft); 513 | if (pos < 20) { 514 | hideSidebar(); 515 | } else { 516 | if (html.classList.contains("sidebar-hidden")) { 517 | showSidebar(); 518 | } 519 | pos = Math.min(pos, window.innerWidth - 100); 520 | document.documentElement.style.setProperty('--sidebar-width', pos + 'px'); 521 | } 522 | } 523 | //on mouseup remove windows functions mousemove & mouseup 524 | function stopResize(e) { 525 | html.classList.remove('sidebar-resizing'); 526 | window.removeEventListener('mousemove', resize, false); 527 | window.removeEventListener('mouseup', stopResize, false); 528 | } 529 | 530 | document.addEventListener('touchstart', function (e) { 531 | firstContact = { 532 | x: e.touches[0].clientX, 533 | time: Date.now() 534 | }; 535 | }, { passive: true }); 536 | 537 | document.addEventListener('touchmove', function (e) { 538 | if (!firstContact) 539 | return; 540 | 541 | var curX = e.touches[0].clientX; 542 | var xDiff = curX - firstContact.x, 543 | tDiff = Date.now() - firstContact.time; 544 | 545 | if (tDiff < 250 && Math.abs(xDiff) >= 150) { 546 | if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300)) 547 | showSidebar(); 548 | else if (xDiff < 0 && curX < 300) 549 | hideSidebar(); 550 | 551 | firstContact = null; 552 | } 553 | }, { passive: true }); 554 | 555 | // Scroll sidebar to current active section 556 | var activeSection = document.getElementById("sidebar").querySelector(".active"); 557 | if (activeSection) { 558 | // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView 559 | activeSection.scrollIntoView({ block: 'center' }); 560 | } 561 | })(); 562 | 563 | (function chapterNavigation() { 564 | document.addEventListener('keydown', function (e) { 565 | if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } 566 | if (window.search && window.search.hasFocus()) { return; } 567 | 568 | switch (e.key) { 569 | case 'ArrowRight': 570 | e.preventDefault(); 571 | var nextButton = document.querySelector('.nav-chapters.next'); 572 | if (nextButton) { 573 | window.location.href = nextButton.href; 574 | } 575 | break; 576 | case 'ArrowLeft': 577 | e.preventDefault(); 578 | var previousButton = document.querySelector('.nav-chapters.previous'); 579 | if (previousButton) { 580 | window.location.href = previousButton.href; 581 | } 582 | break; 583 | } 584 | }); 585 | })(); 586 | 587 | (function clipboard() { 588 | var clipButtons = document.querySelectorAll('.clip-button'); 589 | 590 | function hideTooltip(elem) { 591 | elem.firstChild.innerText = ""; 592 | elem.className = 'fa fa-copy clip-button'; 593 | } 594 | 595 | function showTooltip(elem, msg) { 596 | elem.firstChild.innerText = msg; 597 | elem.className = 'fa fa-copy tooltipped'; 598 | } 599 | 600 | var clipboardSnippets = new ClipboardJS('.clip-button', { 601 | text: function (trigger) { 602 | hideTooltip(trigger); 603 | let playground = trigger.closest("pre"); 604 | return playground_text(playground, false); 605 | } 606 | }); 607 | 608 | Array.from(clipButtons).forEach(function (clipButton) { 609 | clipButton.addEventListener('mouseout', function (e) { 610 | hideTooltip(e.currentTarget); 611 | }); 612 | }); 613 | 614 | clipboardSnippets.on('success', function (e) { 615 | e.clearSelection(); 616 | showTooltip(e.trigger, "Copied!"); 617 | }); 618 | 619 | clipboardSnippets.on('error', function (e) { 620 | showTooltip(e.trigger, "Clipboard error!"); 621 | }); 622 | })(); 623 | 624 | (function scrollToTop () { 625 | var menuTitle = document.querySelector('.menu-title'); 626 | 627 | menuTitle.addEventListener('click', function () { 628 | document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' }); 629 | }); 630 | })(); 631 | 632 | (function controllMenu() { 633 | var menu = document.getElementById('menu-bar'); 634 | 635 | (function controllPosition() { 636 | var scrollTop = document.scrollingElement.scrollTop; 637 | var prevScrollTop = scrollTop; 638 | var minMenuY = -menu.clientHeight - 50; 639 | // When the script loads, the page can be at any scroll (e.g. if you reforesh it). 640 | menu.style.top = scrollTop + 'px'; 641 | // Same as parseInt(menu.style.top.slice(0, -2), but faster 642 | var topCache = menu.style.top.slice(0, -2); 643 | menu.classList.remove('sticky'); 644 | var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster 645 | document.addEventListener('scroll', function () { 646 | scrollTop = Math.max(document.scrollingElement.scrollTop, 0); 647 | // `null` means that it doesn't need to be updated 648 | var nextSticky = null; 649 | var nextTop = null; 650 | var scrollDown = scrollTop > prevScrollTop; 651 | var menuPosAbsoluteY = topCache - scrollTop; 652 | if (scrollDown) { 653 | nextSticky = false; 654 | if (menuPosAbsoluteY > 0) { 655 | nextTop = prevScrollTop; 656 | } 657 | } else { 658 | if (menuPosAbsoluteY > 0) { 659 | nextSticky = true; 660 | } else if (menuPosAbsoluteY < minMenuY) { 661 | nextTop = prevScrollTop + minMenuY; 662 | } 663 | } 664 | if (nextSticky === true && stickyCache === false) { 665 | menu.classList.add('sticky'); 666 | stickyCache = true; 667 | } else if (nextSticky === false && stickyCache === true) { 668 | menu.classList.remove('sticky'); 669 | stickyCache = false; 670 | } 671 | if (nextTop !== null) { 672 | menu.style.top = nextTop + 'px'; 673 | topCache = nextTop; 674 | } 675 | prevScrollTop = scrollTop; 676 | }, { passive: true }); 677 | })(); 678 | (function controllBorder() { 679 | menu.classList.remove('bordered'); 680 | document.addEventListener('scroll', function () { 681 | if (menu.offsetTop === 0) { 682 | menu.classList.remove('bordered'); 683 | } else { 684 | menu.classList.add('bordered'); 685 | } 686 | }, { passive: true }); 687 | })(); 688 | })(); 689 | -------------------------------------------------------------------------------- /theme/css/chrome.css: -------------------------------------------------------------------------------- 1 | /* CSS for UI elements (a.k.a. chrome) */ 2 | 3 | @import 'variables.css'; 4 | 5 | html { 6 | scrollbar-color: var(--scrollbar) var(--bg); 7 | } 8 | #searchresults a, 9 | .content a:link, 10 | a:visited, 11 | a > .hljs { 12 | color: var(--links); 13 | } 14 | 15 | /* 16 | body-container is necessary because mobile browsers don't seem to like 17 | overflow-x on the body tag when there is a tag. 18 | */ 19 | #body-container { 20 | /* 21 | This is used when the sidebar pushes the body content off the side of 22 | the screen on small screens. Without it, dragging on mobile Safari 23 | will want to reposition the viewport in a weird way. 24 | */ 25 | overflow-x: hidden; 26 | } 27 | 28 | /* Menu Bar */ 29 | 30 | #menu-bar, 31 | #menu-bar-hover-placeholder { 32 | z-index: 101; 33 | margin: auto calc(0px - var(--page-padding)); 34 | } 35 | #menu-bar { 36 | position: relative; 37 | display: flex; 38 | flex-wrap: wrap; 39 | background-color: var(--bg); 40 | border-bottom-color: var(--bg); 41 | border-bottom-width: 1px; 42 | border-bottom-style: solid; 43 | } 44 | #menu-bar.sticky, 45 | .js #menu-bar-hover-placeholder:hover + #menu-bar, 46 | .js #menu-bar:hover, 47 | .js.sidebar-visible #menu-bar { 48 | position: -webkit-sticky; 49 | position: sticky; 50 | top: 0 !important; 51 | } 52 | #menu-bar-hover-placeholder { 53 | position: sticky; 54 | position: -webkit-sticky; 55 | top: 0; 56 | height: var(--menu-bar-height); 57 | } 58 | #menu-bar.bordered { 59 | border-bottom-color: var(--table-border-color); 60 | } 61 | #menu-bar i, #menu-bar .icon-button { 62 | position: relative; 63 | padding: 0 8px; 64 | z-index: 10; 65 | line-height: var(--menu-bar-height); 66 | cursor: pointer; 67 | transition: color 0.5s; 68 | } 69 | @media only screen and (max-width: 420px) { 70 | #menu-bar i, #menu-bar .icon-button { 71 | padding: 0 5px; 72 | } 73 | } 74 | 75 | .icon-button { 76 | border: none; 77 | background: none; 78 | padding: 0; 79 | color: inherit; 80 | } 81 | .icon-button i { 82 | margin: 0; 83 | } 84 | 85 | .right-buttons { 86 | margin: 0 15px; 87 | } 88 | .right-buttons a { 89 | text-decoration: none; 90 | } 91 | 92 | .left-buttons { 93 | display: flex; 94 | margin: 0 5px; 95 | } 96 | .no-js .left-buttons { 97 | display: none; 98 | } 99 | 100 | .menu-title { 101 | display: inline-block; 102 | font-weight: 200; 103 | font-size: 2.4rem; 104 | line-height: var(--menu-bar-height); 105 | text-align: center; 106 | margin: 0; 107 | flex: 1; 108 | white-space: nowrap; 109 | overflow: hidden; 110 | text-overflow: ellipsis; 111 | } 112 | .js .menu-title { 113 | cursor: pointer; 114 | } 115 | 116 | .menu-bar, 117 | .menu-bar:visited, 118 | .nav-chapters, 119 | .nav-chapters:visited, 120 | .mobile-nav-chapters, 121 | .mobile-nav-chapters:visited, 122 | .menu-bar .icon-button, 123 | .menu-bar a i { 124 | color: var(--icons); 125 | } 126 | 127 | .menu-bar i:hover, 128 | .menu-bar .icon-button:hover, 129 | .nav-chapters:hover, 130 | .mobile-nav-chapters i:hover { 131 | color: var(--icons-hover); 132 | } 133 | 134 | /* Nav Icons */ 135 | 136 | .nav-chapters { 137 | font-size: 2.5em; 138 | text-align: center; 139 | text-decoration: none; 140 | 141 | position: fixed; 142 | top: 0; 143 | bottom: 0; 144 | margin: 0; 145 | max-width: 150px; 146 | min-width: 90px; 147 | 148 | display: flex; 149 | justify-content: center; 150 | align-content: center; 151 | flex-direction: column; 152 | 153 | transition: color 0.5s, background-color 0.5s; 154 | } 155 | 156 | .nav-chapters:hover { 157 | text-decoration: none; 158 | background-color: var(--theme-hover); 159 | transition: background-color 0.15s, color 0.15s; 160 | } 161 | 162 | .nav-wrapper { 163 | margin-top: 50px; 164 | display: none; 165 | } 166 | 167 | .mobile-nav-chapters { 168 | font-size: 2.5em; 169 | text-align: center; 170 | text-decoration: none; 171 | width: 90px; 172 | border-radius: 5px; 173 | background-color: var(--sidebar-bg); 174 | } 175 | 176 | .previous { 177 | float: left; 178 | } 179 | 180 | .next { 181 | float: right; 182 | right: var(--page-padding); 183 | } 184 | 185 | @media only screen and (max-width: 1080px) { 186 | .nav-wide-wrapper { display: none; } 187 | .nav-wrapper { display: block; } 188 | } 189 | 190 | @media only screen and (max-width: 1380px) { 191 | .sidebar-visible .nav-wide-wrapper { display: none; } 192 | .sidebar-visible .nav-wrapper { display: block; } 193 | } 194 | 195 | /* Inline code */ 196 | 197 | :not(pre) > .hljs { 198 | display: inline; 199 | padding: 0.1em 0.3em; 200 | border-radius: 3px; 201 | } 202 | 203 | :not(pre):not(a) > .hljs { 204 | color: var(--inline-code-color); 205 | overflow-x: initial; 206 | } 207 | 208 | a:hover > .hljs { 209 | text-decoration: underline; 210 | } 211 | 212 | pre { 213 | position: relative; 214 | } 215 | pre > .buttons { 216 | position: absolute; 217 | z-index: 100; 218 | right: 0px; 219 | top: 2px; 220 | margin: 0px; 221 | padding: 2px 0px; 222 | 223 | color: var(--sidebar-fg); 224 | cursor: pointer; 225 | visibility: hidden; 226 | opacity: 0; 227 | transition: visibility 0.1s linear, opacity 0.1s linear; 228 | } 229 | pre:hover > .buttons { 230 | visibility: visible; 231 | opacity: 1 232 | } 233 | pre > .buttons :hover { 234 | color: var(--sidebar-active); 235 | border-color: var(--icons-hover); 236 | background-color: var(--theme-hover); 237 | } 238 | pre > .buttons i { 239 | margin-left: 8px; 240 | } 241 | pre > .buttons button { 242 | cursor: inherit; 243 | margin: 0px 5px; 244 | padding: 3px 5px; 245 | font-size: 14px; 246 | 247 | border-style: solid; 248 | border-width: 1px; 249 | border-radius: 4px; 250 | border-color: var(--icons); 251 | background-color: var(--theme-popup-bg); 252 | transition: 100ms; 253 | transition-property: color,border-color,background-color; 254 | color: var(--icons); 255 | } 256 | @media (pointer: coarse) { 257 | pre > .buttons button { 258 | /* On mobile, make it easier to tap buttons. */ 259 | padding: 0.3rem 1rem; 260 | } 261 | } 262 | pre > code { 263 | padding: 1rem; 264 | } 265 | 266 | /* FIXME: ACE editors overlap their buttons because ACE does absolute 267 | positioning within the code block which breaks padding. The only solution I 268 | can think of is to move the padding to the outer pre tag (or insert a div 269 | wrapper), but that would require fixing a whole bunch of CSS rules. 270 | */ 271 | .hljs.ace_editor { 272 | padding: 0rem 0rem; 273 | } 274 | 275 | pre > .result { 276 | margin-top: 10px; 277 | } 278 | 279 | /* Search */ 280 | 281 | #searchresults a { 282 | text-decoration: none; 283 | } 284 | 285 | mark { 286 | border-radius: 2px; 287 | padding: 0 3px 1px 3px; 288 | margin: 0 -3px -1px -3px; 289 | background-color: var(--search-mark-bg); 290 | transition: background-color 300ms linear; 291 | cursor: pointer; 292 | } 293 | 294 | mark.fade-out { 295 | background-color: rgba(0,0,0,0) !important; 296 | cursor: auto; 297 | } 298 | 299 | .searchbar-outer { 300 | margin-left: auto; 301 | margin-right: auto; 302 | max-width: var(--content-max-width); 303 | } 304 | 305 | #searchbar { 306 | width: 100%; 307 | margin: 5px auto 0px auto; 308 | padding: 10px 16px; 309 | transition: box-shadow 300ms ease-in-out; 310 | border: 1px solid var(--searchbar-border-color); 311 | border-radius: 3px; 312 | background-color: var(--searchbar-bg); 313 | color: var(--searchbar-fg); 314 | } 315 | #searchbar:focus, 316 | #searchbar.active { 317 | box-shadow: 0 0 3px var(--searchbar-shadow-color); 318 | } 319 | 320 | .searchresults-header { 321 | font-weight: bold; 322 | font-size: 1em; 323 | padding: 18px 0 0 5px; 324 | color: var(--searchresults-header-fg); 325 | } 326 | 327 | .searchresults-outer { 328 | margin-left: auto; 329 | margin-right: auto; 330 | max-width: var(--content-max-width); 331 | border-bottom: 1px dashed var(--searchresults-border-color); 332 | } 333 | 334 | ul#searchresults { 335 | list-style: none; 336 | padding-left: 20px; 337 | } 338 | ul#searchresults li { 339 | margin: 10px 0px; 340 | padding: 2px; 341 | border-radius: 2px; 342 | } 343 | ul#searchresults li.focus { 344 | background-color: var(--searchresults-li-bg); 345 | } 346 | ul#searchresults span.teaser { 347 | display: block; 348 | clear: both; 349 | margin: 5px 0 0 20px; 350 | font-size: 0.8em; 351 | } 352 | ul#searchresults span.teaser em { 353 | font-weight: bold; 354 | font-style: normal; 355 | } 356 | 357 | /* Sidebar */ 358 | 359 | .sidebar { 360 | position: fixed; 361 | left: 0; 362 | top: 0; 363 | bottom: 0; 364 | width: var(--sidebar-width); 365 | font-size: 0.875em; 366 | box-sizing: border-box; 367 | -webkit-overflow-scrolling: touch; 368 | overscroll-behavior-y: contain; 369 | background-color: var(--sidebar-bg); 370 | color: var(--sidebar-fg); 371 | } 372 | .sidebar-resizing { 373 | -moz-user-select: none; 374 | -webkit-user-select: none; 375 | -ms-user-select: none; 376 | user-select: none; 377 | } 378 | .js:not(.sidebar-resizing) .sidebar { 379 | transition: transform 0.3s; /* Animation: slide away */ 380 | } 381 | .sidebar code { 382 | line-height: 2em; 383 | } 384 | .sidebar .sidebar-scrollbox { 385 | overflow-y: auto; 386 | position: absolute; 387 | top: 0; 388 | bottom: 0; 389 | left: 0; 390 | right: 0; 391 | padding: 10px 10px; 392 | } 393 | .sidebar .sidebar-resize-handle { 394 | position: absolute; 395 | cursor: col-resize; 396 | width: 0; 397 | right: 0; 398 | top: 0; 399 | bottom: 0; 400 | } 401 | .js .sidebar .sidebar-resize-handle { 402 | cursor: col-resize; 403 | width: 5px; 404 | } 405 | .sidebar-hidden .sidebar { 406 | transform: translateX(calc(0px - var(--sidebar-width))); 407 | } 408 | .sidebar::-webkit-scrollbar { 409 | background: var(--sidebar-bg); 410 | } 411 | .sidebar::-webkit-scrollbar-thumb { 412 | background: var(--scrollbar); 413 | } 414 | 415 | .sidebar-visible .page-wrapper { 416 | transform: translateX(var(--sidebar-width)); 417 | } 418 | @media only screen and (min-width: 620px) { 419 | .sidebar-visible .page-wrapper { 420 | transform: none; 421 | margin-left: var(--sidebar-width); 422 | } 423 | } 424 | 425 | .chapter { 426 | list-style: none outside none; 427 | padding-left: 0; 428 | line-height: 2.2em; 429 | } 430 | 431 | .chapter ol { 432 | width: 100%; 433 | } 434 | 435 | .chapter li { 436 | display: flex; 437 | color: var(--sidebar-non-existant); 438 | } 439 | .chapter li a { 440 | display: block; 441 | padding: 0; 442 | text-decoration: none; 443 | color: var(--sidebar-fg); 444 | } 445 | 446 | .chapter li a:hover { 447 | color: var(--sidebar-active); 448 | } 449 | 450 | .chapter li a.active { 451 | color: var(--sidebar-active); 452 | } 453 | 454 | .chapter li > a.toggle { 455 | cursor: pointer; 456 | display: block; 457 | margin-left: auto; 458 | padding: 0 10px; 459 | user-select: none; 460 | opacity: 0.68; 461 | } 462 | 463 | .chapter li > a.toggle div { 464 | transition: transform 0.5s; 465 | } 466 | 467 | /* collapse the section */ 468 | .chapter li:not(.expanded) + li > ol { 469 | display: none; 470 | } 471 | 472 | .chapter li.chapter-item { 473 | line-height: 1.5em; 474 | margin-top: 0.6em; 475 | } 476 | 477 | .chapter li.expanded > a.toggle div { 478 | transform: rotate(90deg); 479 | } 480 | 481 | .spacer { 482 | width: 100%; 483 | height: 3px; 484 | margin: 5px 0px; 485 | } 486 | .chapter .spacer { 487 | background-color: var(--sidebar-spacer); 488 | } 489 | 490 | @media (-moz-touch-enabled: 1), (pointer: coarse) { 491 | .chapter li a { padding: 5px 0; } 492 | .spacer { margin: 10px 0; } 493 | } 494 | 495 | .section { 496 | list-style: none outside none; 497 | padding-left: 20px; 498 | line-height: 1.9em; 499 | } 500 | 501 | /* Theme Menu Popup */ 502 | 503 | .theme-popup { 504 | position: absolute; 505 | left: 10px; 506 | top: var(--menu-bar-height); 507 | z-index: 1000; 508 | border-radius: 4px; 509 | font-size: 0.7em; 510 | color: var(--fg); 511 | background: var(--theme-popup-bg); 512 | border: 1px solid var(--theme-popup-border); 513 | margin: 0; 514 | padding: 0; 515 | list-style: none; 516 | display: none; 517 | /* Don't let the children's background extend past the rounded corners. */ 518 | overflow: hidden; 519 | } 520 | .theme-popup .default { 521 | color: var(--icons); 522 | } 523 | .theme-popup .theme { 524 | width: 100%; 525 | border: 0; 526 | margin: 0; 527 | padding: 2px 20px; 528 | line-height: 25px; 529 | white-space: nowrap; 530 | text-align: left; 531 | cursor: pointer; 532 | color: inherit; 533 | background: inherit; 534 | font-size: inherit; 535 | } 536 | .theme-popup .theme:hover { 537 | background-color: var(--theme-hover); 538 | } 539 | 540 | .theme-selected::before { 541 | display: inline-block; 542 | content: "✓"; 543 | margin-left: -14px; 544 | width: 14px; 545 | } 546 | -------------------------------------------------------------------------------- /theme/css/general.css: -------------------------------------------------------------------------------- 1 | /* Base styles and content styles */ 2 | 3 | @import 'variables.css'; 4 | 5 | :root { 6 | /* Browser default font-size is 16px, this way 1 rem = 10px */ 7 | font-size: 62.5%; 8 | } 9 | 10 | html { 11 | font-family: "Open Sans", sans-serif; 12 | color: var(--fg); 13 | background-color: var(--bg); 14 | text-size-adjust: none; 15 | -webkit-text-size-adjust: none; 16 | } 17 | 18 | body { 19 | margin: 0; 20 | font-size: 1.6rem; 21 | overflow-x: hidden; 22 | } 23 | 24 | code { 25 | font-family: var(--mono-font) !important; 26 | font-size: var(--code-font-size); 27 | } 28 | 29 | /* make long words/inline code not x overflow */ 30 | main { 31 | overflow-wrap: break-word; 32 | } 33 | 34 | /* make wide tables scroll if they overflow */ 35 | .table-wrapper { 36 | overflow-x: auto; 37 | } 38 | 39 | /* Don't change font size in headers. */ 40 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 41 | font-size: unset; 42 | } 43 | 44 | .left { float: left; } 45 | .right { float: right; } 46 | .boring { opacity: 0.6; } 47 | .hide-boring .boring { display: none; } 48 | .hidden { display: none !important; } 49 | 50 | h2, h3 { margin-top: 2.5em; } 51 | h4, h5 { margin-top: 2em; } 52 | 53 | .header + .header h3, 54 | .header + .header h4, 55 | .header + .header h5 { 56 | margin-top: 1em; 57 | } 58 | 59 | h1:target::before, 60 | h2:target::before, 61 | h3:target::before, 62 | h4:target::before, 63 | h5:target::before, 64 | h6:target::before { 65 | display: inline-block; 66 | content: "»"; 67 | margin-left: -30px; 68 | width: 30px; 69 | } 70 | 71 | /* This is broken on Safari as of version 14, but is fixed 72 | in Safari Technology Preview 117 which I think will be Safari 14.2. 73 | https://bugs.webkit.org/show_bug.cgi?id=218076 74 | */ 75 | :target { 76 | scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); 77 | } 78 | 79 | .page { 80 | outline: 0; 81 | padding: 0 var(--page-padding); 82 | margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ 83 | } 84 | .page-wrapper { 85 | box-sizing: border-box; 86 | } 87 | .js:not(.sidebar-resizing) .page-wrapper { 88 | transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ 89 | } 90 | 91 | .content { 92 | overflow-y: auto; 93 | padding: 0 5px 50px 5px; 94 | } 95 | .content main { 96 | margin-left: auto; 97 | margin-right: auto; 98 | max-width: var(--content-max-width); 99 | } 100 | .content p { line-height: 1.45em; } 101 | .content ol { line-height: 1.45em; } 102 | .content ul { line-height: 1.45em; } 103 | .content a { text-decoration: none; } 104 | .content a:hover { text-decoration: underline; } 105 | .content img, .content video { max-width: 100%; } 106 | .content .header:link, 107 | .content .header:visited { 108 | color: var(--fg); 109 | } 110 | .content .header:link, 111 | .content .header:visited:hover { 112 | text-decoration: none; 113 | } 114 | 115 | table { 116 | margin: 0 auto; 117 | border-collapse: collapse; 118 | } 119 | table td { 120 | padding: 3px 20px; 121 | border: 1px var(--table-border-color) solid; 122 | } 123 | table thead { 124 | background: var(--table-header-bg); 125 | } 126 | table thead td { 127 | font-weight: 700; 128 | border: none; 129 | } 130 | table thead th { 131 | padding: 3px 20px; 132 | } 133 | table thead tr { 134 | border: 1px var(--table-header-bg) solid; 135 | } 136 | /* Alternate background colors for rows */ 137 | table tbody tr:nth-child(2n) { 138 | background: var(--table-alternate-bg); 139 | } 140 | 141 | 142 | blockquote { 143 | margin: 20px 0; 144 | padding: 0 20px; 145 | color: var(--fg); 146 | background-color: var(--quote-bg); 147 | border-top: .1em solid var(--quote-border); 148 | border-bottom: .1em solid var(--quote-border); 149 | } 150 | 151 | kbd { 152 | background-color: var(--table-border-color); 153 | border-radius: 4px; 154 | border: solid 1px var(--theme-popup-border); 155 | box-shadow: inset 0 -1px 0 var(--theme-hover); 156 | display: inline-block; 157 | font-size: var(--code-font-size); 158 | font-family: var(--mono-font); 159 | line-height: 10px; 160 | padding: 4px 5px; 161 | vertical-align: middle; 162 | } 163 | 164 | :not(.footnote-definition) + .footnote-definition, 165 | .footnote-definition + :not(.footnote-definition) { 166 | margin-top: 2em; 167 | } 168 | .footnote-definition { 169 | font-size: 0.9em; 170 | margin: 0.5em 0; 171 | } 172 | .footnote-definition p { 173 | display: inline; 174 | } 175 | 176 | .tooltiptext { 177 | position: absolute; 178 | visibility: hidden; 179 | color: #fff; 180 | background-color: #333; 181 | transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ 182 | left: -8px; /* Half of the width of the icon */ 183 | top: -35px; 184 | font-size: 0.8em; 185 | text-align: center; 186 | border-radius: 6px; 187 | padding: 5px 8px; 188 | margin: 5px; 189 | z-index: 1000; 190 | } 191 | .tooltipped .tooltiptext { 192 | visibility: visible; 193 | } 194 | 195 | .chapter li.part-title { 196 | color: var(--sidebar-fg); 197 | margin: 5px 0px; 198 | font-weight: bold; 199 | } 200 | 201 | .result-no-output { 202 | font-style: italic; 203 | } 204 | -------------------------------------------------------------------------------- /theme/css/print.css: -------------------------------------------------------------------------------- 1 | 2 | #sidebar, 3 | #menu-bar, 4 | .nav-chapters, 5 | .mobile-nav-chapters { 6 | display: none; 7 | } 8 | 9 | #page-wrapper.page-wrapper { 10 | transform: none; 11 | margin-left: 0px; 12 | overflow-y: initial; 13 | } 14 | 15 | #content { 16 | max-width: none; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | .page { 22 | overflow-y: initial; 23 | } 24 | 25 | code { 26 | background-color: #666666; 27 | border-radius: 5px; 28 | 29 | /* Force background to be printed in Chrome */ 30 | -webkit-print-color-adjust: exact; 31 | } 32 | 33 | pre > .buttons { 34 | z-index: 2; 35 | } 36 | 37 | a, a:visited, a:active, a:hover { 38 | color: #4183c4; 39 | text-decoration: none; 40 | } 41 | 42 | h1, h2, h3, h4, h5, h6 { 43 | page-break-inside: avoid; 44 | page-break-after: avoid; 45 | } 46 | 47 | pre, code { 48 | page-break-inside: avoid; 49 | white-space: pre-wrap; 50 | } 51 | 52 | .fa { 53 | display: none !important; 54 | } 55 | -------------------------------------------------------------------------------- /theme/css/variables.css: -------------------------------------------------------------------------------- 1 | 2 | /* Globals */ 3 | 4 | :root { 5 | --sidebar-width: 300px; 6 | --page-padding: 15px; 7 | --content-max-width: 750px; 8 | --menu-bar-height: 50px; 9 | --mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace; 10 | --code-font-size: 0.875em /* please adjust the ace font size accordingly in editor.js */ 11 | } 12 | 13 | /* Themes */ 14 | 15 | .ayu { 16 | --bg: hsl(210, 25%, 8%); 17 | --fg: #c5c5c5; 18 | 19 | --sidebar-bg: #14191f; 20 | --sidebar-fg: #c8c9db; 21 | --sidebar-non-existant: #5c6773; 22 | --sidebar-active: #ffb454; 23 | --sidebar-spacer: #2d334f; 24 | 25 | --scrollbar: var(--sidebar-fg); 26 | 27 | --icons: #737480; 28 | --icons-hover: #b7b9cc; 29 | 30 | --links: #0096cf; 31 | 32 | --inline-code-color: #ffb454; 33 | 34 | --theme-popup-bg: #14191f; 35 | --theme-popup-border: #5c6773; 36 | --theme-hover: #191f26; 37 | 38 | --quote-bg: hsl(226, 15%, 17%); 39 | --quote-border: hsl(226, 15%, 22%); 40 | 41 | --table-border-color: hsl(210, 25%, 13%); 42 | --table-header-bg: hsl(210, 25%, 28%); 43 | --table-alternate-bg: hsl(210, 25%, 11%); 44 | 45 | --searchbar-border-color: #848484; 46 | --searchbar-bg: #424242; 47 | --searchbar-fg: #fff; 48 | --searchbar-shadow-color: #d4c89f; 49 | --searchresults-header-fg: #666; 50 | --searchresults-border-color: #888; 51 | --searchresults-li-bg: #252932; 52 | --search-mark-bg: #e3b171; 53 | } 54 | 55 | .coal { 56 | --bg: hsl(200, 7%, 8%); 57 | --fg: #98a3ad; 58 | 59 | --sidebar-bg: #292c2f; 60 | --sidebar-fg: #a1adb8; 61 | --sidebar-non-existant: #505254; 62 | --sidebar-active: #3473ad; 63 | --sidebar-spacer: #393939; 64 | 65 | --scrollbar: var(--sidebar-fg); 66 | 67 | --icons: #43484d; 68 | --icons-hover: #b3c0cc; 69 | 70 | --links: #2b79a2; 71 | 72 | --inline-code-color: #c5c8c6; 73 | 74 | --theme-popup-bg: #141617; 75 | --theme-popup-border: #43484d; 76 | --theme-hover: #1f2124; 77 | 78 | --quote-bg: hsl(234, 21%, 18%); 79 | --quote-border: hsl(234, 21%, 23%); 80 | 81 | --table-border-color: hsl(200, 7%, 13%); 82 | --table-header-bg: hsl(200, 7%, 28%); 83 | --table-alternate-bg: hsl(200, 7%, 11%); 84 | 85 | --searchbar-border-color: #aaa; 86 | --searchbar-bg: #b7b7b7; 87 | --searchbar-fg: #000; 88 | --searchbar-shadow-color: #aaa; 89 | --searchresults-header-fg: #666; 90 | --searchresults-border-color: #98a3ad; 91 | --searchresults-li-bg: #2b2b2f; 92 | --search-mark-bg: #355c7d; 93 | } 94 | 95 | .light { 96 | --bg: hsl(0, 0%, 100%); 97 | --fg: hsl(0, 0%, 0%); 98 | 99 | --sidebar-bg: #fafafa; 100 | --sidebar-fg: hsl(0, 0%, 0%); 101 | --sidebar-non-existant: #aaaaaa; 102 | --sidebar-active: #1f1fff; 103 | --sidebar-spacer: #f4f4f4; 104 | 105 | --scrollbar: #8F8F8F; 106 | 107 | --icons: #747474; 108 | --icons-hover: #000000; 109 | 110 | --links: #20609f; 111 | 112 | --inline-code-color: #301900; 113 | 114 | --theme-popup-bg: #fafafa; 115 | --theme-popup-border: #cccccc; 116 | --theme-hover: #e6e6e6; 117 | 118 | --quote-bg: hsl(197, 37%, 96%); 119 | --quote-border: hsl(197, 37%, 91%); 120 | 121 | --table-border-color: hsl(0, 0%, 95%); 122 | --table-header-bg: hsl(0, 0%, 80%); 123 | --table-alternate-bg: hsl(0, 0%, 97%); 124 | 125 | --searchbar-border-color: #aaa; 126 | --searchbar-bg: #fafafa; 127 | --searchbar-fg: #000; 128 | --searchbar-shadow-color: #aaa; 129 | --searchresults-header-fg: #666; 130 | --searchresults-border-color: #888; 131 | --searchresults-li-bg: #e4f2fe; 132 | --search-mark-bg: #a2cff5; 133 | } 134 | 135 | .navy { 136 | --bg: hsl(226, 23%, 11%); 137 | --fg: #bcbdd0; 138 | 139 | --sidebar-bg: #282d3f; 140 | --sidebar-fg: #c8c9db; 141 | --sidebar-non-existant: #505274; 142 | --sidebar-active: #2b79a2; 143 | --sidebar-spacer: #2d334f; 144 | 145 | --scrollbar: var(--sidebar-fg); 146 | 147 | --icons: #737480; 148 | --icons-hover: #b7b9cc; 149 | 150 | --links: #2b79a2; 151 | 152 | --inline-code-color: #c5c8c6; 153 | 154 | --theme-popup-bg: #161923; 155 | --theme-popup-border: #737480; 156 | --theme-hover: #282e40; 157 | 158 | --quote-bg: hsl(226, 15%, 17%); 159 | --quote-border: hsl(226, 15%, 22%); 160 | 161 | --table-border-color: hsl(226, 23%, 16%); 162 | --table-header-bg: hsl(226, 23%, 31%); 163 | --table-alternate-bg: hsl(226, 23%, 14%); 164 | 165 | --searchbar-border-color: #aaa; 166 | --searchbar-bg: #aeaec6; 167 | --searchbar-fg: #000; 168 | --searchbar-shadow-color: #aaa; 169 | --searchresults-header-fg: #5f5f71; 170 | --searchresults-border-color: #5c5c68; 171 | --searchresults-li-bg: #242430; 172 | --search-mark-bg: #a2cff5; 173 | } 174 | 175 | .rust { 176 | --bg: hsl(60, 9%, 87%); 177 | --fg: #262625; 178 | 179 | --sidebar-bg: #3b2e2a; 180 | --sidebar-fg: #c8c9db; 181 | --sidebar-non-existant: #505254; 182 | --sidebar-active: #e69f67; 183 | --sidebar-spacer: #45373a; 184 | 185 | --scrollbar: var(--sidebar-fg); 186 | 187 | --icons: #737480; 188 | --icons-hover: #262625; 189 | 190 | --links: #2b79a2; 191 | 192 | --inline-code-color: #6e6b5e; 193 | 194 | --theme-popup-bg: #e1e1db; 195 | --theme-popup-border: #b38f6b; 196 | --theme-hover: #99908a; 197 | 198 | --quote-bg: hsl(60, 5%, 75%); 199 | --quote-border: hsl(60, 5%, 70%); 200 | 201 | --table-border-color: hsl(60, 9%, 82%); 202 | --table-header-bg: #b3a497; 203 | --table-alternate-bg: hsl(60, 9%, 84%); 204 | 205 | --searchbar-border-color: #aaa; 206 | --searchbar-bg: #fafafa; 207 | --searchbar-fg: #000; 208 | --searchbar-shadow-color: #aaa; 209 | --searchresults-header-fg: #666; 210 | --searchresults-border-color: #888; 211 | --searchresults-li-bg: #dec2a2; 212 | --search-mark-bg: #e69f67; 213 | } 214 | 215 | @media (prefers-color-scheme: dark) { 216 | .light.no-js { 217 | --bg: hsl(200, 7%, 8%); 218 | --fg: #98a3ad; 219 | 220 | --sidebar-bg: #292c2f; 221 | --sidebar-fg: #a1adb8; 222 | --sidebar-non-existant: #505254; 223 | --sidebar-active: #3473ad; 224 | --sidebar-spacer: #393939; 225 | 226 | --scrollbar: var(--sidebar-fg); 227 | 228 | --icons: #43484d; 229 | --icons-hover: #b3c0cc; 230 | 231 | --links: #2b79a2; 232 | 233 | --inline-code-color: #c5c8c6; 234 | 235 | --theme-popup-bg: #141617; 236 | --theme-popup-border: #43484d; 237 | --theme-hover: #1f2124; 238 | 239 | --quote-bg: hsl(234, 21%, 18%); 240 | --quote-border: hsl(234, 21%, 23%); 241 | 242 | --table-border-color: hsl(200, 7%, 13%); 243 | --table-header-bg: hsl(200, 7%, 28%); 244 | --table-alternate-bg: hsl(200, 7%, 11%); 245 | 246 | --searchbar-border-color: #aaa; 247 | --searchbar-bg: #b7b7b7; 248 | --searchbar-fg: #000; 249 | --searchbar-shadow-color: #aaa; 250 | --searchresults-header-fg: #666; 251 | --searchresults-border-color: #98a3ad; 252 | --searchresults-li-bg: #2b2b2f; 253 | --search-mark-bg: #355c7d; 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /theme/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TOETOE55/async-rt-book/cf8a5bf2f571307da98aca1ca7bc737d06037f54/theme/favicon.png -------------------------------------------------------------------------------- /theme/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /theme/fonts/OPEN-SANS-LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /theme/fonts/SOURCE-CODE-PRO-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /theme/fonts/fonts.css: -------------------------------------------------------------------------------- 1 | /* Open Sans is licensed under the Apache License, Version 2.0. See http://www.apache.org/licenses/LICENSE-2.0 */ 2 | /* Source Code Pro is under the Open Font License. See https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL */ 3 | 4 | /* open-sans-300 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 5 | @font-face { 6 | font-family: 'Open Sans'; 7 | font-style: normal; 8 | font-weight: 300; 9 | src: local('Open Sans Light'), local('OpenSans-Light'), 10 | url('open-sans-v17-all-charsets-300.woff2') format('woff2'); 11 | } 12 | 13 | /* open-sans-300italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 14 | @font-face { 15 | font-family: 'Open Sans'; 16 | font-style: italic; 17 | font-weight: 300; 18 | src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), 19 | url('open-sans-v17-all-charsets-300italic.woff2') format('woff2'); 20 | } 21 | 22 | /* open-sans-regular - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 23 | @font-face { 24 | font-family: 'Open Sans'; 25 | font-style: normal; 26 | font-weight: 400; 27 | src: local('Open Sans Regular'), local('OpenSans-Regular'), 28 | url('open-sans-v17-all-charsets-regular.woff2') format('woff2'); 29 | } 30 | 31 | /* open-sans-italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 32 | @font-face { 33 | font-family: 'Open Sans'; 34 | font-style: italic; 35 | font-weight: 400; 36 | src: local('Open Sans Italic'), local('OpenSans-Italic'), 37 | url('open-sans-v17-all-charsets-italic.woff2') format('woff2'); 38 | } 39 | 40 | /* open-sans-600 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 41 | @font-face { 42 | font-family: 'Open Sans'; 43 | font-style: normal; 44 | font-weight: 600; 45 | src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), 46 | url('open-sans-v17-all-charsets-600.woff2') format('woff2'); 47 | } 48 | 49 | /* open-sans-600italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 50 | @font-face { 51 | font-family: 'Open Sans'; 52 | font-style: italic; 53 | font-weight: 600; 54 | src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), 55 | url('open-sans-v17-all-charsets-600italic.woff2') format('woff2'); 56 | } 57 | 58 | /* open-sans-700 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 59 | @font-face { 60 | font-family: 'Open Sans'; 61 | font-style: normal; 62 | font-weight: 700; 63 | src: local('Open Sans Bold'), local('OpenSans-Bold'), 64 | url('open-sans-v17-all-charsets-700.woff2') format('woff2'); 65 | } 66 | 67 | /* open-sans-700italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 68 | @font-face { 69 | font-family: 'Open Sans'; 70 | font-style: italic; 71 | font-weight: 700; 72 | src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), 73 | url('open-sans-v17-all-charsets-700italic.woff2') format('woff2'); 74 | } 75 | 76 | /* open-sans-800 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 77 | @font-face { 78 | font-family: 'Open Sans'; 79 | font-style: normal; 80 | font-weight: 800; 81 | src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), 82 | url('open-sans-v17-all-charsets-800.woff2') format('woff2'); 83 | } 84 | 85 | /* open-sans-800italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ 86 | @font-face { 87 | font-family: 'Open Sans'; 88 | font-style: italic; 89 | font-weight: 800; 90 | src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), 91 | url('open-sans-v17-all-charsets-800italic.woff2') format('woff2'); 92 | } 93 | 94 | /* source-code-pro-500 - latin_vietnamese_latin-ext_greek_cyrillic-ext_cyrillic */ 95 | @font-face { 96 | font-family: 'Source Code Pro'; 97 | font-style: normal; 98 | font-weight: 500; 99 | src: url('source-code-pro-v11-all-charsets-500.woff2') format('woff2'); 100 | } 101 | -------------------------------------------------------------------------------- /theme/fonts/open-sans-v17-all-charsets-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TOETOE55/async-rt-book/cf8a5bf2f571307da98aca1ca7bc737d06037f54/theme/fonts/open-sans-v17-all-charsets-300.woff2 -------------------------------------------------------------------------------- /theme/fonts/open-sans-v17-all-charsets-300italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TOETOE55/async-rt-book/cf8a5bf2f571307da98aca1ca7bc737d06037f54/theme/fonts/open-sans-v17-all-charsets-300italic.woff2 -------------------------------------------------------------------------------- /theme/fonts/open-sans-v17-all-charsets-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TOETOE55/async-rt-book/cf8a5bf2f571307da98aca1ca7bc737d06037f54/theme/fonts/open-sans-v17-all-charsets-600.woff2 -------------------------------------------------------------------------------- /theme/fonts/open-sans-v17-all-charsets-600italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TOETOE55/async-rt-book/cf8a5bf2f571307da98aca1ca7bc737d06037f54/theme/fonts/open-sans-v17-all-charsets-600italic.woff2 -------------------------------------------------------------------------------- /theme/fonts/open-sans-v17-all-charsets-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TOETOE55/async-rt-book/cf8a5bf2f571307da98aca1ca7bc737d06037f54/theme/fonts/open-sans-v17-all-charsets-700.woff2 -------------------------------------------------------------------------------- /theme/fonts/open-sans-v17-all-charsets-700italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TOETOE55/async-rt-book/cf8a5bf2f571307da98aca1ca7bc737d06037f54/theme/fonts/open-sans-v17-all-charsets-700italic.woff2 -------------------------------------------------------------------------------- /theme/fonts/open-sans-v17-all-charsets-800.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TOETOE55/async-rt-book/cf8a5bf2f571307da98aca1ca7bc737d06037f54/theme/fonts/open-sans-v17-all-charsets-800.woff2 -------------------------------------------------------------------------------- /theme/fonts/open-sans-v17-all-charsets-800italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TOETOE55/async-rt-book/cf8a5bf2f571307da98aca1ca7bc737d06037f54/theme/fonts/open-sans-v17-all-charsets-800italic.woff2 -------------------------------------------------------------------------------- /theme/fonts/open-sans-v17-all-charsets-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TOETOE55/async-rt-book/cf8a5bf2f571307da98aca1ca7bc737d06037f54/theme/fonts/open-sans-v17-all-charsets-italic.woff2 -------------------------------------------------------------------------------- /theme/fonts/open-sans-v17-all-charsets-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TOETOE55/async-rt-book/cf8a5bf2f571307da98aca1ca7bc737d06037f54/theme/fonts/open-sans-v17-all-charsets-regular.woff2 -------------------------------------------------------------------------------- /theme/fonts/source-code-pro-v11-all-charsets-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TOETOE55/async-rt-book/cf8a5bf2f571307da98aca1ca7bc737d06037f54/theme/fonts/source-code-pro-v11-all-charsets-500.woff2 -------------------------------------------------------------------------------- /theme/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | * An increased contrast highlighting scheme loosely based on the 3 | * "Base16 Atelier Dune Light" theme by Bram de Haan 4 | * (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) 5 | * Original Base16 color scheme by Chris Kempson 6 | * (https://github.com/chriskempson/base16) 7 | */ 8 | 9 | /* Comment */ 10 | .hljs-comment, 11 | .hljs-quote { 12 | color: #575757; 13 | } 14 | 15 | /* Red */ 16 | .hljs-variable, 17 | .hljs-template-variable, 18 | .hljs-attribute, 19 | .hljs-tag, 20 | .hljs-name, 21 | .hljs-regexp, 22 | .hljs-link, 23 | .hljs-name, 24 | .hljs-selector-id, 25 | .hljs-selector-class { 26 | color: #d70025; 27 | } 28 | 29 | /* Orange */ 30 | .hljs-number, 31 | .hljs-meta, 32 | .hljs-built_in, 33 | .hljs-builtin-name, 34 | .hljs-literal, 35 | .hljs-type, 36 | .hljs-params { 37 | color: #b21e00; 38 | } 39 | 40 | /* Green */ 41 | .hljs-string, 42 | .hljs-symbol, 43 | .hljs-bullet { 44 | color: #008200; 45 | } 46 | 47 | /* Blue */ 48 | .hljs-title, 49 | .hljs-section { 50 | color: #0030f2; 51 | } 52 | 53 | /* Purple */ 54 | .hljs-keyword, 55 | .hljs-selector-tag { 56 | color: #9d00ec; 57 | } 58 | 59 | .hljs { 60 | display: block; 61 | overflow-x: auto; 62 | background: #f6f7f6; 63 | color: #000; 64 | } 65 | 66 | .hljs-emphasis { 67 | font-style: italic; 68 | } 69 | 70 | .hljs-strong { 71 | font-weight: bold; 72 | } 73 | 74 | .hljs-addition { 75 | color: #22863a; 76 | background-color: #f0fff4; 77 | } 78 | 79 | .hljs-deletion { 80 | color: #b31d28; 81 | background-color: #ffeef0; 82 | } 83 | -------------------------------------------------------------------------------- /theme/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title }} 7 | {{#if is_print }} 8 | 9 | {{/if}} 10 | {{#if base_url}} 11 | 12 | {{/if}} 13 | 14 | 15 | 16 | {{> head}} 17 | 18 | 19 | 20 | 21 | 22 | {{#if favicon_svg}} 23 | 24 | {{/if}} 25 | {{#if favicon_png}} 26 | 27 | {{/if}} 28 | 29 | 30 | 31 | {{#if print_enable}} 32 | 33 | {{/if}} 34 | 35 | 36 | 37 | {{#if copy_fonts}} 38 | 39 | {{/if}} 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | {{#each additional_css}} 48 | 49 | {{/each}} 50 | 51 | {{#if mathjax_support}} 52 | 53 | 54 | {{/if}} 55 | 56 | 57 |
58 | 59 | 63 | 64 | 65 | 79 | 80 | 81 | 91 | 92 | 93 | 103 | 104 | 110 | 111 |
112 | 113 |
114 | {{> header}} 115 | 116 | 159 | 160 | {{#if search_enabled}} 161 | 171 | {{/if}} 172 | 173 | 174 | 181 | 182 |
183 |
184 | {{{ content }}} 185 |
186 | 187 | 203 |
204 |
205 | 206 | 219 | 220 |
221 | 222 | {{#if live_reload_endpoint}} 223 | 224 | 239 | {{/if}} 240 | 241 | {{#if google_analytics}} 242 | 243 | 258 | {{/if}} 259 | 260 | {{#if playground_line_numbers}} 261 | 264 | {{/if}} 265 | 266 | {{#if playground_copyable}} 267 | 270 | {{/if}} 271 | 272 | {{#if playground_js}} 273 | 274 | 275 | 276 | 277 | 278 | {{/if}} 279 | 280 | {{#if search_js}} 281 | 282 | 283 | 284 | {{/if}} 285 | 286 | 287 | 288 | 289 | 290 | 291 | {{#each additional_js}} 292 | 293 | {{/each}} 294 | 295 | {{#if is_print}} 296 | {{#if mathjax_support}} 297 | 304 | {{else}} 305 | 310 | {{/if}} 311 | {{/if}} 312 | 313 |
314 | 315 | 316 | --------------------------------------------------------------------------------