├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── build ├── examples ├── 01.js ├── 02.js ├── 03.js ├── 04.js ├── 05.js ├── bootstrap.js ├── index.html ├── index.js ├── package-lock.json ├── package.json └── webpack.config.js ├── src └── lib.rs └── watch /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pkg/ 3 | 4 | examples/node_modules/ 5 | examples/dist/ 6 | examples/.cache/ 7 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.3" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "atty" 13 | version = "0.2.11" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | dependencies = [ 16 | "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", 17 | "termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 18 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 19 | ] 20 | 21 | [[package]] 22 | name = "autocfg" 23 | version = "0.1.2" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | 26 | [[package]] 27 | name = "backtrace" 28 | version = "0.3.15" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | dependencies = [ 31 | "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 36 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 37 | ] 38 | 39 | [[package]] 40 | name = "backtrace-sys" 41 | version = "0.1.28" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | dependencies = [ 44 | "cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", 46 | ] 47 | 48 | [[package]] 49 | name = "behaviours-rs" 50 | version = "0.1.1" 51 | dependencies = [ 52 | "js-sys 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", 53 | "kdtree 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 54 | "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", 55 | "serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", 56 | "wasm-bindgen 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 57 | "web-sys 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", 58 | ] 59 | 60 | [[package]] 61 | name = "bumpalo" 62 | version = "2.4.1" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | 65 | [[package]] 66 | name = "cc" 67 | version = "1.0.36" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | 70 | [[package]] 71 | name = "cfg-if" 72 | version = "0.1.7" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | 75 | [[package]] 76 | name = "env_logger" 77 | version = "0.6.1" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | dependencies = [ 80 | "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 81 | "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 82 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 83 | "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 85 | ] 86 | 87 | [[package]] 88 | name = "failure" 89 | version = "0.1.5" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | dependencies = [ 92 | "backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 93 | "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 94 | ] 95 | 96 | [[package]] 97 | name = "failure_derive" 98 | version = "0.1.5" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | dependencies = [ 101 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 103 | "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", 104 | "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 105 | ] 106 | 107 | [[package]] 108 | name = "heck" 109 | version = "0.3.1" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | dependencies = [ 112 | "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 113 | ] 114 | 115 | [[package]] 116 | name = "humantime" 117 | version = "1.2.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | dependencies = [ 120 | "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 121 | ] 122 | 123 | [[package]] 124 | name = "itoa" 125 | version = "0.4.4" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | 128 | [[package]] 129 | name = "js-sys" 130 | version = "0.3.20" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | dependencies = [ 133 | "wasm-bindgen 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 134 | ] 135 | 136 | [[package]] 137 | name = "kdtree" 138 | version = "0.5.1" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | dependencies = [ 141 | "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 142 | ] 143 | 144 | [[package]] 145 | name = "lazy_static" 146 | version = "1.3.0" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | 149 | [[package]] 150 | name = "libc" 151 | version = "0.2.54" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | 154 | [[package]] 155 | name = "log" 156 | version = "0.4.6" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | dependencies = [ 159 | "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 160 | ] 161 | 162 | [[package]] 163 | name = "memchr" 164 | version = "2.2.0" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | 167 | [[package]] 168 | name = "nom" 169 | version = "4.2.3" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | dependencies = [ 172 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 173 | "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 174 | ] 175 | 176 | [[package]] 177 | name = "num-traits" 178 | version = "0.2.6" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | 181 | [[package]] 182 | name = "numtoa" 183 | version = "0.1.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | 186 | [[package]] 187 | name = "proc-macro2" 188 | version = "0.4.30" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | dependencies = [ 191 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 192 | ] 193 | 194 | [[package]] 195 | name = "quick-error" 196 | version = "1.2.2" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | 199 | [[package]] 200 | name = "quote" 201 | version = "0.6.12" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | dependencies = [ 204 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 205 | ] 206 | 207 | [[package]] 208 | name = "redox_syscall" 209 | version = "0.1.54" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | 212 | [[package]] 213 | name = "redox_termios" 214 | version = "0.1.1" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | dependencies = [ 217 | "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", 218 | ] 219 | 220 | [[package]] 221 | name = "regex" 222 | version = "1.1.6" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | dependencies = [ 225 | "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", 226 | "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 227 | "regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", 228 | "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 229 | "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 230 | ] 231 | 232 | [[package]] 233 | name = "regex-syntax" 234 | version = "0.6.6" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | dependencies = [ 237 | "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 238 | ] 239 | 240 | [[package]] 241 | name = "rustc-demangle" 242 | version = "0.1.14" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | 245 | [[package]] 246 | name = "ryu" 247 | version = "0.2.8" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | 250 | [[package]] 251 | name = "serde" 252 | version = "1.0.91" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | 255 | [[package]] 256 | name = "serde_derive" 257 | version = "1.0.91" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | dependencies = [ 260 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 261 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 262 | "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", 263 | ] 264 | 265 | [[package]] 266 | name = "serde_json" 267 | version = "1.0.39" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | dependencies = [ 270 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 271 | "ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 272 | "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", 273 | ] 274 | 275 | [[package]] 276 | name = "sourcefile" 277 | version = "0.1.4" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | 280 | [[package]] 281 | name = "syn" 282 | version = "0.15.34" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | dependencies = [ 285 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 286 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 287 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 288 | ] 289 | 290 | [[package]] 291 | name = "synstructure" 292 | version = "0.10.1" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | dependencies = [ 295 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 296 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 297 | "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", 298 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 299 | ] 300 | 301 | [[package]] 302 | name = "termcolor" 303 | version = "1.0.4" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | dependencies = [ 306 | "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 307 | ] 308 | 309 | [[package]] 310 | name = "termion" 311 | version = "1.5.2" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | dependencies = [ 314 | "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", 315 | "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 316 | "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", 317 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 318 | ] 319 | 320 | [[package]] 321 | name = "thread_local" 322 | version = "0.3.6" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | dependencies = [ 325 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 326 | ] 327 | 328 | [[package]] 329 | name = "ucd-util" 330 | version = "0.1.3" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | 333 | [[package]] 334 | name = "unicode-segmentation" 335 | version = "1.2.1" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | 338 | [[package]] 339 | name = "unicode-xid" 340 | version = "0.1.0" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | 343 | [[package]] 344 | name = "utf8-ranges" 345 | version = "1.0.2" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | 348 | [[package]] 349 | name = "version_check" 350 | version = "0.1.5" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | 353 | [[package]] 354 | name = "wasm-bindgen" 355 | version = "0.2.43" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | dependencies = [ 358 | "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", 359 | "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", 360 | "wasm-bindgen-macro 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 361 | ] 362 | 363 | [[package]] 364 | name = "wasm-bindgen-backend" 365 | version = "0.2.43" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | dependencies = [ 368 | "bumpalo 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 369 | "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 370 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 371 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 372 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 373 | "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", 374 | "wasm-bindgen-shared 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 375 | ] 376 | 377 | [[package]] 378 | name = "wasm-bindgen-macro" 379 | version = "0.2.43" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | dependencies = [ 382 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 383 | "wasm-bindgen-macro-support 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 384 | ] 385 | 386 | [[package]] 387 | name = "wasm-bindgen-macro-support" 388 | version = "0.2.43" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | dependencies = [ 391 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 392 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 393 | "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", 394 | "wasm-bindgen-backend 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 395 | "wasm-bindgen-shared 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 396 | ] 397 | 398 | [[package]] 399 | name = "wasm-bindgen-shared" 400 | version = "0.2.43" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | 403 | [[package]] 404 | name = "wasm-bindgen-webidl" 405 | version = "0.2.43" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | dependencies = [ 408 | "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 409 | "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 410 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 411 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 412 | "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", 413 | "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", 414 | "wasm-bindgen-backend 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 415 | "weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 416 | ] 417 | 418 | [[package]] 419 | name = "web-sys" 420 | version = "0.3.20" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | dependencies = [ 423 | "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 424 | "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 425 | "js-sys 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", 426 | "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 427 | "wasm-bindgen 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 428 | "wasm-bindgen-webidl 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 429 | ] 430 | 431 | [[package]] 432 | name = "weedle" 433 | version = "0.8.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | dependencies = [ 436 | "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 437 | ] 438 | 439 | [[package]] 440 | name = "winapi" 441 | version = "0.3.7" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | dependencies = [ 444 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 445 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 446 | ] 447 | 448 | [[package]] 449 | name = "winapi-i686-pc-windows-gnu" 450 | version = "0.4.0" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | 453 | [[package]] 454 | name = "winapi-util" 455 | version = "0.1.2" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | dependencies = [ 458 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 459 | ] 460 | 461 | [[package]] 462 | name = "winapi-x86_64-pc-windows-gnu" 463 | version = "0.4.0" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | 466 | [[package]] 467 | name = "wincolor" 468 | version = "1.0.1" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | dependencies = [ 471 | "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 472 | "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 473 | ] 474 | 475 | [metadata] 476 | "checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" 477 | "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" 478 | "checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" 479 | "checksum backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f106c02a3604afcdc0df5d36cc47b44b55917dbaf3d808f71c163a0ddba64637" 480 | "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" 481 | "checksum bumpalo 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4639720be048090544634e0402490838995ccdc9d2fe648f528f30d3c33ae71f" 482 | "checksum cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a0c56216487bb80eec9c4516337b2588a4f2a2290d72a1416d930e4dcdb0c90d" 483 | "checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" 484 | "checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a" 485 | "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" 486 | "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" 487 | "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 488 | "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" 489 | "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" 490 | "checksum js-sys 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "e9c0d432259f651d765d888e30164c096ddbae13c89e56dd1d02a719e020efa8" 491 | "checksum kdtree 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b63d659081717fe428fbaf9549559083956450194e57e8d28498a0dfa31b3b04" 492 | "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" 493 | "checksum libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "c6785aa7dd976f5fbf3b71cfd9cd49d7f783c1ff565a858d71031c6c313aa5c6" 494 | "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" 495 | "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" 496 | "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" 497 | "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" 498 | "checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" 499 | "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 500 | "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" 501 | "checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" 502 | "checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" 503 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 504 | "checksum regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f0a0bcab2fd7d1d7c54fa9eae6f43eddeb9ce2e7352f8518a814a4f65d60c58" 505 | "checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96" 506 | "checksum rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ccc78bfd5acd7bf3e89cffcf899e5cb1a52d6fafa8dec2739ad70c9577a57288" 507 | "checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f" 508 | "checksum serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "a72e9b96fa45ce22a4bc23da3858dfccfd60acd28a25bcd328a98fdd6bea43fd" 509 | "checksum serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "101b495b109a3e3ca8c4cbe44cf62391527cdfb6ba15821c5ce80bcd5ea23f9f" 510 | "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" 511 | "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" 512 | "checksum syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)" = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe" 513 | "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" 514 | "checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" 515 | "checksum termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dde0593aeb8d47accea5392b39350015b5eccb12c0d98044d856983d89548dea" 516 | "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 517 | "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" 518 | "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" 519 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 520 | "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" 521 | "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 522 | "checksum wasm-bindgen 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "546e4ab1bf7f9a3532d21472efd72d01a23f55abd885c60b165f393394dbad95" 523 | "checksum wasm-bindgen-backend 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "7b84bedebfd6ae3522cce59dec6b52ee6c53ceeaae8541668c15b9f42df8ecab" 524 | "checksum wasm-bindgen-macro 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "f2a033fc6bfd5e486a488b0e19d7d1bb29e667ebb91db85f698381a8aa831786" 525 | "checksum wasm-bindgen-macro-support 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "fba68375ef8f095c4a169c093c95ed2e1b5e44f7872f3bcbcafe2c51b4a80480" 526 | "checksum wasm-bindgen-shared 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "321949f4d7f7bf7a49dccd464bdc46581b180f761d9505e4943926d50b2a4a64" 527 | "checksum wasm-bindgen-webidl 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "b8c2c2b45b827f96657beea954a5430d37da4cf477d2874595f5f0a6ad027980" 528 | "checksum web-sys 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "47ad2ecfe3793a87a0aa49562ad6f01cb3af3c870213283bc60032ec8dd7e62a" 529 | "checksum weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26a4c67f132386d965390b8a734d5d10adbcd30eb5cc74bd9229af8b83f10044" 530 | "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" 531 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 532 | "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" 533 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 534 | "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" 535 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "behaviours-rs" 3 | version = "0.1.1" 4 | authors = ["Szymon Kaliski "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | js-sys = "0.3.20" 12 | kdtree = "0.5.1" 13 | serde = "^1.0.59" 14 | serde_derive = "^1.0.59" 15 | 16 | [dependencies.wasm-bindgen] 17 | version = "0.2.43" 18 | features = ["serde-serialize"] 19 | 20 | [dependencies.web-sys] 21 | version = "0.3" 22 | features = ["console"] 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Szymon Kaliski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # behaviours-rs 2 | 3 | **Work in progress!** 4 | 5 | Rust + wasm library for declarative modeling of simple particle behaviours. 6 | 7 | ## Installation 8 | 9 | `npm install behaviours-rs` 10 | 11 | ## Example 12 | 13 | 10000 points repelling each other: 14 | 15 | ```js 16 | import { createSimulation } from "behaviours-rs"; 17 | 18 | const [width, height] = [600, 600]; 19 | 20 | const numPoints = 10000; 21 | 22 | const points = new Float32Array(numPoints * 2); 23 | 24 | for (let i = 0; i < numPoints; i++) { 25 | const x = Math.random() * width; 26 | const y = Math.random() * height; 27 | 28 | points[i * 2] = x; 29 | points[i * 2 + 1] = y; 30 | } 31 | 32 | // main behaviour modeling 33 | const behaviours = [ 34 | ["repel", { f: 0.3, r: 50.0 }], 35 | ["dampen", { f: 0.1 }] 36 | ]; 37 | 38 | const simulation = createSimulation(points, 2, behaviours); 39 | 40 | const canvas = document.createElement("canvas"); 41 | canvas.width = width; 42 | canvas.height = height; 43 | document.body.appendChild(canvas); 44 | 45 | const ctx = canvas.getContext("2d"); 46 | 47 | const loop = () => { 48 | ctx.clearRect(0, 0, 600, 600); 49 | 50 | simulation.step(); 51 | 52 | const positions = simulation.get(); 53 | 54 | for (let i = 0; i < positions.length; i += 2) { 55 | const x = positions[i]; 56 | const y = positions[i + 1]; 57 | 58 | ctx.fillRect(x, y, 1, 1); 59 | } 60 | 61 | requestAnimationFrame(loop); 62 | }; 63 | 64 | loop(); 65 | ``` 66 | 67 | Running examples: 68 | 69 | ```bash 70 | cd ./examples/ 71 | npm install 72 | npm start 73 | ``` 74 | 75 | ## API 76 | 77 | ### Constructor 78 | 79 | #### `const simulation = createSimulation(points, dimensions, behaviours)` - creates new simulation 80 | 81 | - `points` - `Float32Array` of flat `x`, `y`, `z` (if in 3d) positions: `[ x1,y1,z1, x2,y2,z2, ... ]` 82 | - `dimensions` - `2` or `3`, can be omitted, defaults to `2` 83 | - `behaviours` - tree of behaviours 84 | 85 | ### Functions 86 | 87 | - `simulation.step()` - single step of simulation 88 | - `simulation.get()` - returns all positions (same format as `points` when creating simulation) 89 | - `simulation.getIf(test)` - returns all points matching provided test (look at `"if"` behaviour) 90 | - `simulation.setMeta(idx, key, value)` - sets additional `key`/`value` for specified point 91 | - `simulation.getMeta(idx, key)` - returns value for provided `key` or empty string 92 | 93 | ### Behaviours 94 | 95 | - `["repel", { f, r, p }]` 96 | - `f` - force, ideally between `0.0` and `1.0` 97 | - `r` - impact radius 98 | - `p` - position when the repelling happens, if ommited the points repel each other 99 | - `["attract", { f, r, p }]` 100 | - `f` - force, ideally between `0.0` and `1.0` 101 | - `r` - impact radius 102 | - `p` - position when the attraction happens, if ommited the points attract each other 103 | - `["dampen", { f }]"` - dampens velocity 104 | - `f` - force, ideally between `0.0` and `1.0` 105 | - `["if", { test }, children]` - executes `children` when `test` passes 106 | - `test`: [`op`, `key`, `value`], where `op` is either `"==`" or `"!="`, and `key`/`value` are this point's metadata 107 | - `["collide", { test, r }, children]` - executes `children` when points collide in given `r`, optionally passing a `test` 108 | - `test` - same as in `"if"`, optional 109 | - `r` - radius of collision 110 | - `["set", { ke, value }]"` - sets `key`/`value` metadata on current point 111 | 112 | For `collide` and `if` usage look into [`examples/03.js`](./examples/03.js). 113 | 114 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { execSync } = require("child_process"); 4 | const fs = require("fs"); 5 | 6 | execSync("wasm-pack build"); 7 | 8 | const index = `import { Simulation } from "./behaviours_rs.js"; 9 | 10 | function processBehaviours(behaviours) { 11 | return behaviours.reduce( 12 | (memo, [behaviour, params = {}, children]) => [ 13 | ...memo, 14 | { 15 | behaviour, 16 | params, 17 | ...(children ? { children: processBehaviours(children) } : undefined) 18 | } 19 | ], 20 | [] 21 | ); 22 | } 23 | 24 | export function createSimulation(points, dims, behaviours) { 25 | if (!behaviours) { 26 | behaviours = dims; 27 | dims = 2; 28 | } 29 | 30 | behaviours = processBehaviours(behaviours); 31 | 32 | const simulation = Simulation.create(points, dims, behaviours); 33 | 34 | simulation.replaceBehaviours = function(behaviours) { 35 | simulation._replaceBehaviours(processBehaviours(behaviours)); 36 | } 37 | 38 | return simulation; 39 | } 40 | `; 41 | 42 | fs.writeFileSync("./pkg/index.js", index, { encoding: "utf-8" }); 43 | 44 | const packageJson = require("./pkg/package.json"); 45 | 46 | packageJson.module = "index.js"; 47 | packageJson.files.push("index.js"); 48 | 49 | fs.writeFileSync("./pkg/package.json", JSON.stringify(packageJson, null, 2), { 50 | encoding: "utf-8" 51 | }); 52 | -------------------------------------------------------------------------------- /examples/01.js: -------------------------------------------------------------------------------- 1 | import { createSimulation } from "behaviours-rs"; 2 | 3 | export default () => { 4 | const [width, height] = [600, 600]; 5 | 6 | const numPoints = 10000; 7 | // const numPoints = 10; 8 | 9 | const points = new Float32Array(numPoints * 2); 10 | 11 | for (let i = 0; i < numPoints; i++) { 12 | const x = Math.random() * width; 13 | const y = Math.random() * height; 14 | 15 | points[i * 2] = x; 16 | points[i * 2 + 1] = y; 17 | } 18 | 19 | const behaviours = [ 20 | ["repel", { f: 0.3, r: 50.0 }], 21 | ["dampen", { f: 0.1 }] 22 | ]; 23 | 24 | const simulation = createSimulation(points, 2, behaviours); 25 | 26 | const canvas = document.createElement("canvas"); 27 | canvas.width = width; 28 | canvas.height = height; 29 | document.body.appendChild(canvas); 30 | 31 | const ctx = canvas.getContext("2d"); 32 | 33 | const loop = () => { 34 | ctx.clearRect(0, 0, 600, 600); 35 | 36 | simulation.step(); 37 | 38 | const positions = simulation.get(); 39 | 40 | for (let i = 0; i < positions.length; i += 2) { 41 | const x = positions[i]; 42 | const y = positions[i + 1]; 43 | 44 | ctx.fillRect(x, y, 1, 1); 45 | } 46 | 47 | requestAnimationFrame(loop); 48 | }; 49 | 50 | loop(); 51 | }; 52 | -------------------------------------------------------------------------------- /examples/02.js: -------------------------------------------------------------------------------- 1 | import { createSimulation } from "behaviours-rs"; 2 | 3 | export default () => { 4 | const [width, height] = [600, 600]; 5 | 6 | const numPoints = 10000; 7 | 8 | const points = new Float32Array(numPoints * 2); 9 | 10 | for (let i = 0; i < numPoints; i++) { 11 | const x = Math.random() * width; 12 | const y = Math.random() * height; 13 | 14 | points[i * 2] = x; 15 | points[i * 2 + 1] = y; 16 | } 17 | 18 | const behaviours = [ 19 | // ["attract", { p: [600, 300], f: 0.1 }], 20 | ["attract", { p: [600, 300], f: 0.1, r: 300 * 300 }], 21 | ["attract", { p: [0, 300], f: 0.1, r: 300 * 300 }], 22 | ["attract", { p: [300, 0], f: 0.1, r: 300 * 300 }], 23 | ["attract", { p: [300, 600], f: 0.1, r: 300 * 300 }], 24 | ["repel", { f: 0.3, r: 50.0 }], 25 | ["dampen", { f: 0.1 }] 26 | ]; 27 | 28 | const simulation = createSimulation(points, 2, behaviours); 29 | 30 | const canvas = document.createElement("canvas"); 31 | canvas.width = width; 32 | canvas.height = height; 33 | document.body.appendChild(canvas); 34 | 35 | const ctx = canvas.getContext("2d"); 36 | 37 | const loop = () => { 38 | ctx.clearRect(0, 0, 600, 600); 39 | 40 | simulation.step(); 41 | 42 | const positions = simulation.get(); 43 | 44 | for (let i = 0; i < positions.length; i += 2) { 45 | const x = positions[i]; 46 | const y = positions[i + 1]; 47 | 48 | ctx.fillRect(x, y, 1, 1); 49 | } 50 | 51 | requestAnimationFrame(loop); 52 | }; 53 | 54 | loop(); 55 | }; 56 | -------------------------------------------------------------------------------- /examples/03.js: -------------------------------------------------------------------------------- 1 | import { createSimulation } from "behaviours-rs"; 2 | 3 | export default () => { 4 | const [width, height] = [600, 600]; 5 | 6 | const numPoints = 10000; 7 | 8 | const points = new Float32Array(numPoints * 2); 9 | 10 | const randomPointOnCircle = r => { 11 | const angle = Math.random() * Math.PI * 2; 12 | return [Math.cos(angle) * r, Math.sin(angle) * r]; 13 | }; 14 | 15 | for (let i = 0; i < numPoints; i++) { 16 | if (i === 0) { 17 | points[i * 2] = 300; 18 | points[i * 2 + 1] = 300; 19 | } else { 20 | const [x, y] = randomPointOnCircle(Math.random() * 600 + 200); 21 | 22 | points[i * 2] = x + 300; 23 | points[i * 2 + 1] = y + 300; 24 | } 25 | } 26 | 27 | const behaviours = [ 28 | [ 29 | "if", 30 | { test: ["!=", "static", "true"] }, 31 | [ 32 | ["attract", { p: [300, 300], f: 0.2 }], 33 | ["dampen", { f: 0.1 }], 34 | [ 35 | "collide", 36 | { r: 10.0, test: ["==", "static", "true"] }, 37 | [ 38 | ["set", { key: "static", value: "true" }], 39 | ["stop"] 40 | ] 41 | ] 42 | ] 43 | ] 44 | ]; 45 | 46 | const simulation = createSimulation(points, 2, behaviours); 47 | 48 | simulation.setMeta(0, "static", "true"); 49 | 50 | const canvas = document.createElement("canvas"); 51 | canvas.width = width; 52 | canvas.height = height; 53 | document.body.appendChild(canvas); 54 | 55 | const ctx = canvas.getContext("2d"); 56 | 57 | const loop = () => { 58 | ctx.clearRect(0, 0, 600, 600); 59 | 60 | simulation.step(); 61 | 62 | const positions = simulation.get(); 63 | 64 | for (let i = 0; i < positions.length; i += 2) { 65 | const x = positions[i]; 66 | const y = positions[i + 1]; 67 | 68 | ctx.fillRect(x, y, 1, 1); 69 | } 70 | 71 | requestAnimationFrame(loop); 72 | }; 73 | 74 | loop(); 75 | }; 76 | -------------------------------------------------------------------------------- /examples/04.js: -------------------------------------------------------------------------------- 1 | import { createSimulation } from "behaviours-rs"; 2 | 3 | const THREE = require("three"); 4 | const OrbitControls = require("three-orbitcontrols"); 5 | 6 | export default () => { 7 | const vertexShader = ` 8 | void main() { 9 | vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); 10 | gl_PointSize = 2.0; 11 | gl_Position = projectionMatrix * mvPosition; 12 | } 13 | `; 14 | 15 | const fragmentShader = ` 16 | void main() { 17 | gl_FragColor = vec4(1.0); 18 | } 19 | `; 20 | 21 | let camera, controls, scene, renderer, simulation, geometry; 22 | 23 | const onWindowResize = () => { 24 | camera.aspect = window.innerWidth / window.innerHeight; 25 | camera.updateProjectionMatrix(); 26 | renderer.setSize(window.innerWidth, window.innerHeight); 27 | }; 28 | 29 | const init = () => { 30 | camera = new THREE.PerspectiveCamera( 31 | 80, 32 | window.innerWidth / window.innerHeight 33 | ); 34 | 35 | camera.position.z = 300; 36 | 37 | scene = new THREE.Scene(); 38 | 39 | const shaderMaterial = new THREE.ShaderMaterial({ 40 | vertexShader, 41 | fragmentShader, 42 | blending: THREE.AdditiveBlending, 43 | depthTest: false, 44 | transparent: true, 45 | vertexColors: true 46 | }); 47 | 48 | const radius = 100; 49 | const numPoints = 10000; 50 | 51 | const points = new Float32Array(numPoints * 3); 52 | 53 | for (let i = 0; i < numPoints; i++) { 54 | points[i * 3 + 0] = (Math.random() * 2 - 1) * radius; 55 | points[i * 3 + 1] = (Math.random() * 2 - 1) * radius; 56 | points[i * 3 + 2] = (Math.random() * 2 - 1) * radius; 57 | } 58 | 59 | const behaviours = [ 60 | ["repel", { f: 0.8, r: 50.0 }], 61 | ["attract", { f: 0.2, p: [500, 0, 0], r: 520 * 520 }], 62 | ["attract", { f: 0.2, p: [-500, 0, 0], r: 520 * 520 }], 63 | ["dampen", { f: 0.05 }] 64 | ]; 65 | 66 | simulation = createSimulation(points, 3, behaviours); 67 | 68 | geometry = new THREE.BufferGeometry(); 69 | 70 | geometry.addAttribute( 71 | "position", 72 | new THREE.Float32BufferAttribute(points, 3) 73 | ); 74 | 75 | const particleSystem = new THREE.Points(geometry, shaderMaterial); 76 | scene.add(particleSystem); 77 | 78 | renderer = new THREE.WebGLRenderer(); 79 | renderer.setPixelRatio(window.devicePixelRatio); 80 | renderer.setSize(window.innerWidth, window.innerHeight); 81 | 82 | document.body.appendChild(renderer.domElement); 83 | document.body.style.margin = 0; 84 | 85 | controls = new OrbitControls(camera, renderer.domElement); 86 | 87 | window.addEventListener("resize", onWindowResize, false); 88 | }; 89 | 90 | const loop = () => { 91 | simulation.step(); 92 | 93 | const positions = simulation.get(); 94 | 95 | for (let i = 0; i < positions.length; i += 1) { 96 | geometry.attributes.position.array[i] = positions[i]; 97 | } 98 | 99 | geometry.attributes.position.needsUpdate = true; 100 | 101 | renderer.render(scene, camera); 102 | 103 | requestAnimationFrame(loop); 104 | }; 105 | 106 | init(); 107 | loop(); 108 | }; 109 | -------------------------------------------------------------------------------- /examples/05.js: -------------------------------------------------------------------------------- 1 | import { createSimulation } from "behaviours-rs"; 2 | 3 | const THREE = require("three"); 4 | const OrbitControls = require("three-orbitcontrols"); 5 | 6 | export default () => { 7 | const vertexShader = ` 8 | void main() { 9 | vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); 10 | gl_PointSize = 2.0; 11 | gl_Position = projectionMatrix * mvPosition; 12 | } 13 | `; 14 | 15 | const fragmentShader = ` 16 | void main() { 17 | gl_FragColor = vec4(1.0); 18 | } 19 | `; 20 | 21 | let camera, controls, scene, renderer, simulation, geometry; 22 | 23 | const randomSpherePoint = radius => { 24 | const u = Math.random(); 25 | const v = Math.random(); 26 | const theta = 2 * Math.PI * u; 27 | const phi = Math.acos(2 * v - 1); 28 | const x = radius * Math.sin(phi) * Math.cos(theta); 29 | const y = radius * Math.sin(phi) * Math.sin(theta); 30 | const z = radius * Math.cos(phi); 31 | 32 | return [x, y, z]; 33 | }; 34 | 35 | const onWindowResize = () => { 36 | camera.aspect = window.innerWidth / window.innerHeight; 37 | camera.updateProjectionMatrix(); 38 | renderer.setSize(window.innerWidth, window.innerHeight); 39 | }; 40 | 41 | const init = () => { 42 | camera = new THREE.PerspectiveCamera( 43 | 80, 44 | window.innerWidth / window.innerHeight 45 | ); 46 | 47 | camera.position.z = 300; 48 | 49 | scene = new THREE.Scene(); 50 | 51 | const shaderMaterial = new THREE.ShaderMaterial({ 52 | vertexShader, 53 | fragmentShader, 54 | blending: THREE.AdditiveBlending, 55 | depthTest: false, 56 | transparent: true, 57 | vertexColors: true 58 | }); 59 | 60 | const radius = 500; 61 | const numPoints = 20000; 62 | 63 | const points = new Float32Array(numPoints * 3); 64 | 65 | for (let i = 0; i < numPoints; i++) { 66 | const [x, y, z] = 67 | i === 0 ? [0, 0, 0] : randomSpherePoint(Math.random() * radius + 500); 68 | 69 | points[i * 3 + 0] = x; 70 | points[i * 3 + 1] = y; 71 | 72 | // full sphere 73 | // points[i * 3 + 2] = z; 74 | 75 | // ring 76 | points[i * 3 + 2] = Math.random() * 60 - 30; 77 | } 78 | 79 | const behaviours = [ 80 | [ 81 | "if", 82 | { test: ["!=", "static", "true"] }, 83 | [ 84 | ["attract", { p: [0, 0, 0], f: 0.2 }], 85 | ["dampen", { f: 0.1 }], 86 | [ 87 | "collide", 88 | { r: 5.0, test: ["==", "static", "true"] }, 89 | [ 90 | ["set", { key: "static", value: "true" }], 91 | ["stop"] 92 | ] 93 | ] 94 | ] 95 | ] 96 | ]; 97 | 98 | simulation = createSimulation(points, 3, behaviours); 99 | simulation.setMeta(0, "static", "true"); 100 | 101 | geometry = new THREE.BufferGeometry(); 102 | 103 | geometry.addAttribute( 104 | "position", 105 | new THREE.Float32BufferAttribute(points, 3) 106 | ); 107 | 108 | const particleSystem = new THREE.Points(geometry, shaderMaterial); 109 | scene.add(particleSystem); 110 | 111 | renderer = new THREE.WebGLRenderer(); 112 | renderer.setPixelRatio(window.devicePixelRatio); 113 | renderer.setSize(window.innerWidth, window.innerHeight); 114 | 115 | document.body.appendChild(renderer.domElement); 116 | document.body.style.margin = 0; 117 | 118 | controls = new OrbitControls(camera, renderer.domElement); 119 | 120 | window.addEventListener("resize", onWindowResize, false); 121 | }; 122 | 123 | const loop = () => { 124 | simulation.step(); 125 | 126 | const positions = simulation.get(); 127 | 128 | for (let i = 0; i < positions.length; i += 1) { 129 | geometry.attributes.position.array[i] = positions[i]; 130 | } 131 | 132 | geometry.attributes.position.needsUpdate = true; 133 | 134 | renderer.render(scene, camera); 135 | 136 | requestAnimationFrame(loop); 137 | }; 138 | 139 | init(); 140 | loop(); 141 | }; 142 | -------------------------------------------------------------------------------- /examples/bootstrap.js: -------------------------------------------------------------------------------- 1 | import("./index.js").catch(e => 2 | console.error("Error importing `index.js`:", e) 3 | ); 4 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | import ex01 from "./01.js"; 2 | import ex02 from "./02.js"; 3 | import ex03 from "./03.js"; 4 | import ex04 from "./04.js"; 5 | import ex05 from "./05.js"; 6 | 7 | const examples = { 8 | "1": [ex01, "basic repulsion"], 9 | "2": [ex02, "multiple point attraction"], 10 | "3": [ex03, "diffusion-limited aggregation"], 11 | "4": [ex04, "basic 3d example"], 12 | "5": [ex05, "diffusion-limited aggregation in 3d"] 13 | }; 14 | 15 | const searchValue = document.location.search.replace("?", ""); 16 | 17 | if (!searchValue) { 18 | const wrapper = document.createElement("div"); 19 | 20 | wrapper.innerHTML = Object.keys(examples) 21 | .map(key => { 22 | const note = examples[key][1]; 23 | return `
${key} — ${note}
`; 26 | }) 27 | .join("\n"); 28 | 29 | wrapper.style.fontFamily = "sans-serif"; 30 | wrapper.style.fontSize = "18px"; 31 | 32 | document.body.appendChild(wrapper); 33 | } else { 34 | examples[searchValue][0](); 35 | } 36 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "behaviours-rs-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "keywords": [], 7 | "author": "Szymon Kaliski (http://szymonkaliski.com)", 8 | "license": "MIT", 9 | "scripts": { 10 | "start": "webpack-dev-server" 11 | }, 12 | "devDependencies": { 13 | "copy-webpack-plugin": "^5.0.3", 14 | "webpack": "^4.31.0", 15 | "webpack-cli": "^3.3.2", 16 | "webpack-dev-server": "^3.3.1" 17 | }, 18 | "dependencies": { 19 | "behaviours-rs": "file:../pkg", 20 | "three": "^0.105.1", 21 | "three-orbitcontrols": "^2.102.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 2 | const path = require("path"); 3 | 4 | module.exports = { 5 | entry: "./bootstrap.js", 6 | devServer: { 7 | port: 3000 8 | }, 9 | output: { 10 | path: path.resolve(__dirname, "dist"), 11 | filename: "bootstrap.js" 12 | }, 13 | mode: "development", 14 | plugins: [new CopyWebpackPlugin(["index.html"])] 15 | }; 16 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_derive; 3 | 4 | extern crate js_sys; 5 | extern crate kdtree; 6 | extern crate wasm_bindgen; 7 | extern crate web_sys; 8 | 9 | use js_sys::WebAssembly; 10 | use kdtree::distance::squared_euclidean; 11 | use kdtree::KdTree; 12 | use std::collections::HashMap; 13 | use wasm_bindgen::prelude::*; 14 | use wasm_bindgen::JsCast; 15 | 16 | macro_rules! log { 17 | ( $( $t:tt )* ) => { 18 | web_sys::console::log_1(&format!( $( $t )* ).into()); 19 | } 20 | } 21 | 22 | #[derive(Deserialize, Debug, Clone, Copy)] 23 | struct Vector { 24 | x: f32, 25 | y: f32, 26 | #[serde(default)] 27 | z: f32, 28 | } 29 | 30 | impl Default for Vector { 31 | fn default() -> Self { 32 | Vector { 33 | x: 0.0, 34 | y: 0.0, 35 | z: 0.0, 36 | } 37 | } 38 | } 39 | 40 | impl Vector { 41 | fn sub(mut self, other_vector: Vector) -> Vector { 42 | self.x -= other_vector.x; 43 | self.y -= other_vector.y; 44 | self.z -= other_vector.z; 45 | self 46 | } 47 | 48 | fn add(mut self, other_vector: Vector) -> Vector { 49 | self.x += other_vector.x; 50 | self.y += other_vector.y; 51 | self.z += other_vector.z; 52 | self 53 | } 54 | 55 | fn div_n(mut self, n: f32) -> Vector { 56 | if n == 0.0 { 57 | return self; 58 | } 59 | 60 | self.x /= n; 61 | self.y /= n; 62 | self.z /= n; 63 | self 64 | } 65 | 66 | fn mul_n(mut self, n: f32) -> Vector { 67 | self.x *= n; 68 | self.y *= n; 69 | self.z *= n; 70 | self 71 | } 72 | 73 | fn magnitude(self) -> f32 { 74 | (self.x * self.x + self.y * self.y + self.z * self.z).sqrt() 75 | } 76 | 77 | fn normalize(self) -> Vector { 78 | let mag = self.magnitude(); 79 | self.div_n(mag) 80 | } 81 | } 82 | 83 | type MetaMap = HashMap; 84 | 85 | #[derive(Deserialize, Debug, Clone)] 86 | struct Point { 87 | pos: Vector, 88 | vel: Vector, 89 | meta: MetaMap, 90 | } 91 | 92 | #[derive(Deserialize, Debug)] 93 | struct Test { 94 | op: String, 95 | key: String, 96 | value: String, 97 | } 98 | 99 | impl Default for Test { 100 | fn default() -> Self { 101 | Test { 102 | op: "NOP".to_string(), 103 | key: "".to_string(), 104 | value: "".to_string(), 105 | } 106 | } 107 | } 108 | 109 | #[derive(Deserialize, Debug)] 110 | struct Params { 111 | f: Option, 112 | r: Option, 113 | p: Option, 114 | test: Option, 115 | key: Option, 116 | value: Option, 117 | } 118 | 119 | #[derive(Deserialize, Debug)] 120 | struct BehaviourNode { 121 | behaviour: String, 122 | params: Params, 123 | 124 | #[serde(default)] 125 | children: Vec, 126 | } 127 | 128 | #[wasm_bindgen] 129 | pub struct Simulation { 130 | behaviours: Vec, 131 | points: Vec, 132 | tree2d: Option>, 133 | tree3d: Option>, 134 | dims: usize, 135 | } 136 | 137 | fn tree2d_from_points(points: &[Point]) -> KdTree { 138 | let mut tree = KdTree::new(2); 139 | 140 | for (i, point) in points.iter().enumerate() { 141 | tree.add([point.pos.x, point.pos.y], i).unwrap(); 142 | } 143 | 144 | tree 145 | } 146 | 147 | fn tree3d_from_points(points: &[Point]) -> KdTree { 148 | let mut tree = KdTree::new(3); 149 | 150 | for (i, point) in points.iter().enumerate() { 151 | tree.add([point.pos.x, point.pos.y, point.pos.z], i) 152 | .unwrap(); 153 | } 154 | 155 | tree 156 | } 157 | 158 | #[wasm_bindgen] 159 | impl Simulation { 160 | pub fn create( 161 | points_flat: &js_sys::Float32Array, 162 | dims: usize, 163 | behaviours: &JsValue, 164 | ) -> Simulation { 165 | let behaviours: Vec = behaviours.into_serde().unwrap(); 166 | 167 | // log!("[behaviours] {:?}", behaviours); 168 | 169 | let mut points_tmp = Vec::new(); 170 | let mut final_points: Vec = [].to_vec(); 171 | 172 | points_flat.for_each(&mut |n, _, _| points_tmp.push(n)); 173 | 174 | for i in 0..points_tmp.len() / dims { 175 | let x = points_tmp[i * dims]; 176 | let y = points_tmp[i * dims + 1]; 177 | let z = if dims == 2 { 178 | 0.0 179 | } else { 180 | points_tmp[i * dims + 2] 181 | }; 182 | 183 | final_points.push(Point { 184 | pos: Vector { x, y, z }, 185 | vel: Vector::default(), 186 | meta: MetaMap::new(), 187 | }) 188 | } 189 | 190 | // log!("[final_points] {:?}", final_points); 191 | 192 | let tree2d = if dims == 2 { 193 | Some(tree2d_from_points(&final_points)) 194 | } else { 195 | None 196 | }; 197 | 198 | let tree3d = if dims == 3 { 199 | Some(tree3d_from_points(&final_points)) 200 | } else { 201 | None 202 | }; 203 | 204 | Simulation { 205 | points: final_points, 206 | behaviours, 207 | tree2d, 208 | tree3d, 209 | dims, 210 | } 211 | } 212 | 213 | #[wasm_bindgen(js_name = setMeta)] 214 | pub fn set_meta(&mut self, idx: usize, key: String, value: String) { 215 | self.points[idx].meta.insert(key, value); 216 | } 217 | 218 | #[wasm_bindgen(js_name = getMeta)] 219 | pub fn get_meta(self, idx: usize, key: String) -> String { 220 | self.points[idx] 221 | .meta 222 | .get(&key) 223 | .unwrap_or(&"".to_string()) 224 | .to_string() 225 | } 226 | 227 | #[wasm_bindgen(js_name = _replaceBehaviours)] 228 | pub fn replace_behaviors(&mut self, behaviours: &JsValue) { 229 | self.behaviours = behaviours.into_serde().unwrap(); 230 | } 231 | 232 | fn vel_for_pos_or_others(&self, params: &Params, point: &Point) -> Vector { 233 | let mut vel_mod = Vector::default(); 234 | 235 | match params.p { 236 | Some(p) => { 237 | let should_impact = if params.r.unwrap_or(0.0) != 0.0 { 238 | let d = squared_euclidean( 239 | &[p.x, p.y, p.z], 240 | &[point.pos.x, point.pos.y, point.pos.z], 241 | ); 242 | 243 | d < params.r.unwrap_or(0.0) 244 | } else { 245 | true 246 | }; 247 | 248 | if should_impact { 249 | vel_mod = p.sub(point.pos).normalize().mul_n(params.f.unwrap_or(0.0)); 250 | } 251 | } 252 | 253 | None => { 254 | let nearby_points = if self.dims == 2 { 255 | self.tree2d 256 | .as_ref() 257 | .unwrap() 258 | .within( 259 | &[point.pos.x, point.pos.y], 260 | params.r.unwrap_or(0.0), 261 | &squared_euclidean, 262 | ) 263 | .unwrap() 264 | } else { 265 | self.tree3d 266 | .as_ref() 267 | .unwrap() 268 | .within( 269 | &[point.pos.x, point.pos.y, point.pos.z], 270 | params.r.unwrap_or(0.0), 271 | &squared_euclidean, 272 | ) 273 | .unwrap() 274 | }; 275 | 276 | for (_, nearby_idx) in nearby_points { 277 | vel_mod = vel_mod.sub( 278 | point 279 | .pos 280 | .sub(self.points[*nearby_idx].pos) 281 | .normalize() 282 | .mul_n(params.f.unwrap_or(0.0)), 283 | ); 284 | } 285 | } 286 | } 287 | 288 | vel_mod 289 | } 290 | 291 | fn process_behaviours(&self, point: &Point, behaviours: &[BehaviourNode]) -> (Vector, MetaMap) { 292 | let mut vel = point.vel.clone(); 293 | let mut meta = point.meta.clone(); 294 | 295 | let empty_value = "".to_string(); 296 | 297 | for b in behaviours { 298 | if b.behaviour == "if" { 299 | let test_default = &Test::default(); 300 | let test = b.params.test.as_ref().unwrap_or(test_default); 301 | 302 | let point_value = point.meta.get(&test.key).unwrap_or(&empty_value); 303 | 304 | if (test.op == "!=" && test.value != *point_value) 305 | || (test.op == "==" && test.value == *point_value) 306 | { 307 | let (child_vel, child_meta) = self.process_behaviours(point, &b.children); 308 | 309 | // FIXME: not sure about this, why `vel = vel.add(child_vel);` doesn't work? 310 | vel = child_vel; 311 | meta.extend(child_meta); 312 | } 313 | } 314 | 315 | if b.behaviour == "repel" { 316 | let b_vel = self.vel_for_pos_or_others(&b.params, point); 317 | vel = vel.sub(b_vel); 318 | } 319 | 320 | if b.behaviour == "attract" { 321 | let b_vel = self.vel_for_pos_or_others(&b.params, point); 322 | vel = vel.add(b_vel); 323 | } 324 | 325 | if b.behaviour == "dampen" { 326 | vel = vel.mul_n(1.0 - b.params.f.unwrap_or(0.0)); 327 | } 328 | 329 | if b.behaviour == "collide" { 330 | let test_default = &Test::default(); 331 | let test = b.params.test.as_ref().unwrap_or(test_default); 332 | 333 | let nearby_points = if self.dims == 2 { 334 | self.tree2d 335 | .as_ref() 336 | .unwrap() 337 | .within( 338 | &[point.pos.x, point.pos.y], 339 | b.params.r.unwrap_or(0.0), 340 | &squared_euclidean, 341 | ) 342 | .unwrap() 343 | } else { 344 | self.tree3d 345 | .as_ref() 346 | .unwrap() 347 | .within( 348 | &[point.pos.x, point.pos.y, point.pos.z], 349 | b.params.r.unwrap_or(0.0), 350 | &squared_euclidean, 351 | ) 352 | .unwrap() 353 | }; 354 | 355 | let mut did_collide_passing_test = test.value == "NOP".to_string(); 356 | 357 | if !did_collide_passing_test { 358 | for (_, nearby_idx) in nearby_points { 359 | let point_value = self.points[*nearby_idx] 360 | .meta 361 | .get(&test.key) 362 | .unwrap_or(&empty_value); 363 | 364 | if (test.op == "!=" && test.value != *point_value) 365 | || (test.op == "==" && test.value == *point_value) 366 | { 367 | did_collide_passing_test = true; 368 | break; 369 | } 370 | } 371 | } 372 | 373 | if did_collide_passing_test { 374 | let (child_vel, child_meta) = self.process_behaviours(point, &b.children); 375 | 376 | // FIXME: not sure about this, why `vel = vel.add(child_vel);` doesn't work? 377 | vel = child_vel; 378 | meta.extend(child_meta); 379 | } 380 | } 381 | 382 | if b.behaviour == "set" { 383 | match (&b.params.key, &b.params.value) { 384 | (Some(key), Some(value)) => meta.insert(key.clone(), value.clone()), 385 | _ => None, 386 | }; 387 | } 388 | 389 | if b.behaviour == "stop" { 390 | vel = Vector::default(); 391 | } 392 | } 393 | 394 | (vel, meta) 395 | } 396 | 397 | pub fn step(&mut self) { 398 | for i in 0..self.points.len() { 399 | let (new_vel, new_meta) = self.process_behaviours(&self.points[i], &self.behaviours); 400 | 401 | self.points[i].vel = new_vel; 402 | self.points[i].pos = self.points[i].pos.add(self.points[i].vel); 403 | self.points[i].meta.extend(new_meta); 404 | } 405 | 406 | if self.dims == 2 { 407 | self.tree2d = Some(tree2d_from_points(&self.points)); 408 | } else { 409 | self.tree3d = Some(tree3d_from_points(&self.points)); 410 | } 411 | } 412 | 413 | pub fn get(&self) -> js_sys::Float32Array { 414 | let points_flat = self.points.iter().fold(Vec::new(), |mut values, p| { 415 | values.push(p.pos.x); 416 | values.push(p.pos.y); 417 | if self.dims == 3 { 418 | values.push(p.pos.z); 419 | } 420 | 421 | values 422 | }); 423 | 424 | let points: &[f32] = &points_flat; 425 | 426 | let memory_buffer = wasm_bindgen::memory() 427 | .dyn_into::() 428 | .unwrap() 429 | .buffer(); 430 | 431 | let points_location = points.as_ptr() as u32 / 4; 432 | 433 | js_sys::Float32Array::new(&memory_buffer) 434 | .subarray(points_location, points_location + points.len() as u32) 435 | } 436 | 437 | #[wasm_bindgen(js_name = getIf)] 438 | pub fn get_if(&self, test: &JsValue) -> js_sys::Float32Array { 439 | let test: Test = test.into_serde().unwrap(); 440 | 441 | let empty_value = "".to_string(); 442 | 443 | let points_flat = self.points.iter().fold(Vec::new(), |mut values, p| { 444 | let point_value = p.meta.get(&test.key).unwrap_or(&empty_value); 445 | 446 | if (test.op == "!=" && test.value != *point_value) 447 | || (test.op == "==" && test.value == *point_value) 448 | { 449 | values.push(p.pos.x); 450 | values.push(p.pos.y); 451 | if self.dims == 3 { 452 | values.push(p.pos.z); 453 | } 454 | } 455 | 456 | values 457 | }); 458 | 459 | let points: &[f32] = &points_flat; 460 | 461 | let memory_buffer = wasm_bindgen::memory() 462 | .dyn_into::() 463 | .unwrap() 464 | .buffer(); 465 | 466 | let points_location = points.as_ptr() as u32 / 4; 467 | 468 | js_sys::Float32Array::new(&memory_buffer) 469 | .subarray(points_location, points_location + points.len() as u32) 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /watch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cargo watch -i "pkg/*" -i "test/*" -i "target/*" -s "./build" 4 | --------------------------------------------------------------------------------